Как вы используете printf в сборке x86 в Visual Studio 2017?

Необработанное исключение по адресу 0x777745BA (ntdll.dll) в MASM1.exe: 0xC0000005: место записи нарушения прав доступа 0x00000014. Я использую сборку x86 в Visual Studios 2017, и она продолжает возвращать эту ошибку.

Я включил все библиотеки и установил Windows 10 SDK. Я в основном озадачен тем, почему это возвращает эту ошибку в строке 21. Он даже открывает пустое окно, а затем сразу же закрывает его, возвращая ошибку.

            .586
            .MODEL FLAT
            .STACK 4096
            includelib libcmt.lib
            includelib libvcruntime.lib
            includelib libucrt.lib
            includelib legacy_stdio_definitions.lib
            EXTERN  printf:PROC
            EXTERN  scanf:PROC

            .DATA
                format BYTE "Enter a number", 0

            .CODE

            main PROC
                sub esp, 4
                push offset format
                call printf
                add esp, 4
                ret
            main ENDP
            END

Я создал проект VS 2017 C++, создающий консольную программу Win32. В свойствах проекта / Linker / Advanced / entry point я установил точку входа main.


person Mike D    schedule 12.05.2019    source источник
comment
Да, я уже использовал ret, и это ничего не исправило.   -  person Mike D    schedule 12.05.2019
comment
Затем обновите свой вопрос, указав версию с ret; эта версия имеет очевидную огромную ошибку.   -  person Peter Cordes    schedule 12.05.2019
comment
Действительно, вы все еще получаете writing location 0x00000014 как ошибку с ret? По какой инструкции? Это внутри printf или после основного возврата?   -  person Peter Cordes    schedule 12.05.2019
comment
Просто сожалел об этом. Любые другие предложения? Я все это время использовал отладчик, пытаясь понять это, и не могу найти решение ошибки.   -  person Mike D    schedule 12.05.2019
comment
В нем конкретно говорится о строке 19, когда я добавляю 4 обратно в esp.   -  person Mike D    schedule 12.05.2019
comment
Эта инструкция не записывает память, она только изменяет регистр, поэтому эта инструкция не может ошибиться таким образом. Используйте представление дизассемблирования в отладчике, чтобы увидеть фактические выполняемые инструкции, а не только исходный код ассемблера.   -  person Peter Cordes    schedule 12.05.2019
comment
Вызывающий отвечает за очистку аргументов printf. Таким образом, вам нужно add esp, 8 перед возвратом очистить как переданный аргумент format, так и скорректировать sub esp, 4   -  person Michael Burr    schedule 12.05.2019
comment
Я немного удивлен этим сборкам и ссылкам. Я бы подумал, что вам понадобится .MODEL FLAT, C, чтобы правильно связать main, поскольку он действительно должен разрешаться в _main. Вы говорите, что используете Visual Studio 2017. Используете ли вы пользовательские команды сборки и компоновки, и если да, то какие команды вы используете для сборки и компоновки? У меня есть подозрение, что вы не создали проект MSVC VS2017 с IDE обычным способом (добавив MASM в качестве зависимости сборки), а затем добавили файл ASM с этим кодом.   -  person Michael Petch    schedule 12.05.2019
comment
У меня есть подозрение, что вам удалось обойти запуск кода запуска среды выполнения C (поэтому printf не будет работать должным образом), и вам удалось сделать точку входа для вашей программы основной (с параметр командной строки?). Это помимо того факта, что add esp, 4 должно быть add esp, 8. Я смог воспроизвести вашу проблему, заставив main быть точкой входа, связавшись с опцией /ENTRY. Другая возможность заключается в том, что то, что вы показываете здесь для ассемблерного кода, не является тем, что вы на самом деле используете.   -  person Michael Petch    schedule 12.05.2019
comment
Я могу только порекомендовать как минимум. Измените .MODEL FLAT на .MODEL FLAT, C, затем измените ADD ESP, 4 (после printf) на ADD ESP, 8 и, если вы связываетесь с /ENTRY, удалите эту опцию. Это то, что я рекомендую, если вы не показываете свои команды сборки и компоновки (и все варианты).   -  person Michael Petch    schedule 12.05.2019
comment
Не редактируйте свой вопрос, чтобы дать решение. Разрешается и поощряется создание ответа на свой вопрос.   -  person Michael Petch    schedule 12.05.2019
comment
Вы также случайно не изменили свойства проекта / Microsoft Macro Assembler / Advanced / Calling Convention, чтобы было установлено значение Использовать соглашение о вызовах в стиле C (/Gd) (/Gd)?   -  person Michael Petch    schedule 12.05.2019
comment
@MichaelPetch, какое соглашение о вызовах мы используем для собственного кода, здесь не имеет значения. мы не можем изменить соглашение о вызове внешней функции (printf)   -  person RbMm    schedule 12.05.2019
comment
@RbMm Я задал вопрос не поэтому. Изменение соглашения о вызовах также влияет на то, использует ли MASM по умолчанию символы подчеркивания или нет. В опубликованном коде не используется .MODEL FLAT, C, поэтому для того, чтобы этот код собирался и связывался без неопределенных ссылок, необходимо было установить что-то внешнее, чтобы переопределить поведение по умолчанию. Например, без изменения поведения по умолчанию EXTERN printf:PROC должно было быть EXTERN _printf:PROC, а main PROC должно было быть _main PROC и т. д.   -  person Michael Petch    schedule 12.05.2019


Ответы (2)


У вас есть sub esp,4 и push перед вызовом, поэтому, чтобы восстановить указатель стека, указывающий на адрес возврата, вам нужно add esp,8 перед ret вместо add esp, 4

(printf — это функция с переменными аргументами, поэтому она не извлекает собственные аргументы из стека. Она использует соглашение о вызовах cdecl.)

Или лучше удалить sub esp,4.

32-разрядная версия Windows поддерживает только 4-байтовое выравнивание стека, поэтому вам не нужно делать ничего дополнительно с ESP до push/call, чтобы повторно выровнять указатель стека до call. И вы не используете эти 4 байта, которые вы зарезервировали для чего-либо.


Обновление: МайклПетч заметил, что ваша программа, вероятно, дает сбой внутри printf, потому что вы вызвали ее без инициализации libc. Вероятно, вы создаете свою программу с этой функцией в качестве точки входа, а не с вызовом из обычного кода запуска C. (И что отладчик Visual Studio ошибочно сообщает, что сбой произошел в строке после call, а не где на самом деле произошла авария.)


Похоже, ваше сообщение об ошибке все еще относится к первой версии вопроса, где вы пропустили ret! В этом случае выполнение просто падает с конца main на следующие байты, декодируя их как инструкции. Наверное нули.

00 00 декодируется как add [eax], al, а eax содержит 14 из возвращаемого значения printf. (printf возвращает количество символов printf, а ваша строка формата имеет длину 14 байтов).

Но сообщение об ошибке касается записи адреса 0x14, который представляет собой десятичное число 20 (16 + 4), так что мое первое предположение не совсем совпадает. Если вы хотите знать, используйте отладчик, чтобы найти инструкцию, которая на самом деле дает сбой, и посмотрите значения регистров. Возможно, вам придется использовать представление дизассемблирования вместо представления исходного кода, особенно для версии, в которой вы упасть с конца main.


Вероятно, вы не получите вывода на экран, если stdout буферизуется строкой, а ваша строка формата printf не заканчивается новой строкой. Таким образом, строка все еще находится в буфере ввода-вывода при сбое. (Хотя IIRC, printf в Windows не такой, и делает fflush() буфер, даже если он не заканчивается новой строкой.)

Используйте puts для печати фиксированной строки (без преобразования %) и добавления новой строки. то есть puts(x) похоже на printf("%s\n", x).

person Peter Cordes    schedule 12.05.2019
comment
Отладчик ошибочно покажет ошибку в строке 21 (add esp, 4), хотя на самом деле она возникла внутри вызова printf, и способ, которым я ее воспроизвел (вплоть до значения 0x00000014), состоит в том, чтобы предотвратить C код запуска среды выполнения (путем создания точки входа для программы main), а не то, что является точкой входа по умолчанию в коде среды выполнения C. Мое мнение, что проблема больше в том, как они собирают и связывают информацию, которую они не предоставляют :( - person Michael Petch; 12.05.2019
comment
Извините, что не указал, что для зависимостей сборки моего проекта установлен флажок masm. Моя предварительная точка входа компоновщика установлена ​​​​на main. Система компоновщика по-прежнему консольная. Я не уверен, какую еще информацию вы хотите, чтобы я предоставил о том, что я связываю - person Mike D; 12.05.2019
comment
@MikeD: Используйте @username, чтобы уведомить людей, которым вы отвечаете. Вы отвечаете под моим ответом, поэтому я получаю пинг, а МайклПетч, вероятно, нет. Тем не менее, размещение этой информации в вашем вопросе было бы началом. Обычно вы хотите, чтобы точкой входа был стартовый код CRT (C Runtime Startup), чтобы он мог запускать функции инициализации библиотеки перед вызовом вашего main. - person Peter Cordes; 12.05.2019
comment
@Michael Petch и Peter Cordes Это была точка входа, я удалил основную точку входа, и вот что ее исправило. Спасибо, ребята, за ваше время и информацию об этом. Вы все мне очень помогли. Еще раз спасибо - person Mike D; 12.05.2019

Это решение, предложенное ОП в редактировании их вопроса:

Я решил проблему, очистив параметр linker->advanced->Entry point, который я установил на main в свойствах проекта.

Как предлагает @PeterCordes в своем ответе, лучшее решение для устранения проблемы, связанной со стеком, — это удалить файл sub esp, 4.

person Community    schedule 12.05.2019