Вопрос 1
Вы ошибаетесь из-за того, что это связано с дескрипторами файлов. Это не то, о чем сообщается.
Как вы пояснили в комментариях, 24 - это число, которое отображается, когда вы echo $?
после запуска программы. Это код выхода из программы; обычно значение возвращается из функции main
или передается в exit()
. Это может быть все, что вы хотите, и обычно не соответствует значению errno
.
Так почему ваша программа выдает код выхода 24? Если возвращается main
, то кодом выхода является его возвращаемое значение. Ожидается, что возвращаемое значение функции останется в регистре rax
, когда оно вернется (при условии, что оно имеет целочисленный тип или тип указателя). Но вы никогда не трогаете регистр rax
, поэтому он по-прежнему содержит значение, оставленное там, когда возвращается printf
. Теперь printf
возвращает количество успешно напечатанных символов, которое для выбранной вами строки равно ... 24 (считайте их).
Это просто совпадение, что 24 также происходит с кодом errno для "слишком большого количества дескрипторов открытых файлов" - это совершенно не связано.
Если вы хотите выйти с кодом выхода 0 чтобы сигнализировать об успехе, вы должны xor rax, rax
как раз перед вашим ret
.
Вы не показали точный код, который вы использовали при изменении его на int 0x80
, чтобы самостоятельно вызвать системный вызов _exit
. Но в этом случае код выхода будет таким, какой есть в регистре ebx
при выполнении системного вызова. Возможно, ваш код ставит ноль в ebx
, или, может быть, вам повезло, и он уже содержит ноль.
(Примечание: int 0x80
- это интерфейс для 32-битных системных вызовов, и он не подходит в 64-битной программе, о которой вы, похоже, пишете, хотя она может работать в некоторых случаях. Интерфейс 64-битного системного вызова использует инструкцию syscall
и объясняется в A.2.1 ABI.)
Вопрос 2
Вы должны выровнять стек.
При вызове функции C из сборки (как printf
в данном случае) требуется, чтобы x86-64 ABI Section 3.2.2 выровнял стек по 16-байтовой границе. Стек выравнивается соответствующим образом перед вызовом main
, а адрес возврата, выдвинутый call
, вычитает 8 байтов.
Так что, если вы вообще не касаетесь стека в вашем коде, он не будет правильно выровнен при вызове printf
, и это может привести к тому, что он обработает sh. (Ассемблер не поможет вам в этом; это не его работа.) Но когда вы извлекаете pu sh rbp
, это вычитает еще 8 байтов и выравнивает стек должным образом. Так что либо оставьте этот код внутри, либо выровняйте стек самостоятельно.
И в любом случае, имейте в виду, что если вы измените свой код на pu sh больше содержимого в стеке, вам придется соответствующим образом изменить его, прежде чем выполнять какие-либо вызовы функций.