Запуск 32-разрядного кода сборки на 64-разрядном процессоре Linux и 64-разрядном процессоре: объясните аномалию

у меня интересная проблема.Я забыл, что использую 64-битную машину и ОС и написал 32-битный код сборки. Я не знаю, как написать 64-битный код.

это 32-разрядный сборочный код x86 для ассемблера Gnu (синтаксис AT&T) в Linux.

//hello.S
#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

.data
hellostr:
    .ascii "hello wolrdn";
helloend:

.text
.globl _start

_start:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int x80

    ret

теперь этот код должен работать нормально на 32-битном процессоре и 32-битной ОС, верно? Как мы знаем, 64-битные процессоры обратно совместимы с 32-битными процессорами. Так что это тоже не проблема. Проблема возникает из-за различия в механизме системных вызовов и вызовов в 64-разрядной ОС и 32-разрядной ОС. Я не знаю, почему, но они изменили номера системных вызовов между 32-битным linux и 64-битным linux.

asm / unistd_32.h определяет:

#define __NR_write        4
#define __NR_exit         1

asm / unistd_64.h определяет:

#define __NR_write              1
#define __NR_exit               60

в любом случае использование макросов вместо прямых номеров окупается. Его обеспечение правильных номеров системных вызовов.

когда я собираю и связываю и запускаю программу.

$cpp hello.S hello.s //pre-processor
$as hello.s -o hello.o //assemble
$ld hello.o // linker : converting relocatable to executable

его не печать helloworld.

в gdb его показ:

  • программа вышла с кодом 01.

Я не знаю, как отлаживать в gdb. используя учебник, я попытался отладить его и выполнить инструкцию по проверке регистров инструкций на каждом шаге. его всегда показывает мне "программа вышла с 01". Было бы здорово, если бы некоторые могли показать мне, как отлаживать это.

(gdb) break _start
Note: breakpoint -10 also set at pc 0x4000b0.
Breakpoint 8 at 0x4000b0
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Temporary breakpoint 9 (main) pending.
Starting program: /home/claws/helloworld 

Program exited with code 01.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
8       breakpoint     keep y   0x00000000004000b0 <_start>
9       breakpoint     del  y   <PENDING>          main

я пробовал использовать strace. Это его вывод:

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0
write(0, NULL, 12 <unfinished ... exit status 1>
  1. объясните параметры write(0, NULL, 12) системный вызов на выходе strace?
  2. что ровно происходит? Я хочу знать, почему ровно его выход с exitstatus=1?
  3. может кто-нибудь показать мне, как отлаживать эту программу с помощью gdb?
  4. почему они изменили номера системных вызовов?
  5. пожалуйста, измените эту программу соответствующим образом, чтобы она могла работать правильно на этой машине.

EDIT:

после прочтения ответа Павла Р. Я проверил свои файлы

claws@claws-desktop:~$ file ./hello.o 
./hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

claws@claws-desktop:~$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

Я согласен с ним, что они должны быть ELF 32-битными перемещаемыми и исполняемыми. Но это не ответ на мои вопросы. Все мои вопросы остаются вопросами. Что именно происходит в этом случае? Может ли кто-нибудь ответить на мои вопросы и предоставить версию x86-64 этого кода?

3 ответов


помните, что все по умолчанию на 64-битной ОС имеет тенденцию предполагать 64-бит. Вам нужно убедиться, что Вы (a) используете 32-разрядные версии вашего #includes, где это необходимо (b) связывание с 32-разрядными библиотеками и (c) создание 32-разрядного исполняемого файла. Вероятно, это поможет, если вы покажете содержимое своего файла makefile, если он у вас есть, или команды, которые вы используете для создания этого примера.

FWIW я немного изменил ваш код (_start -> main):

#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

    .data
hellostr:
    .ascii "hello wolrd\n" ;
helloend:

    .text
    .globl main

main:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int x80

    ret

и построил его так:

$ gcc -Wall test.S -m32 -o test

проверено, что у нас есть 32-разрядный исполняемый файл:

$ file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), not stripped

и, похоже, работает нормально:

$ ./test
hello wolrd

Как отметил Пол, Если вы хотите создать 32-разрядные двоичные файлы в 64-разрядной системе, вам нужно использовать флаг-m32, который может быть недоступен по умолчанию на вашей установке (некоторые 64-разрядные дистрибутивы Linux не включают поддержку 32-разрядного компилятора/компоновщика/lib по умолчанию).

с другой стороны, вы можете вместо этого построить свой код как 64-разрядный, в этом случае вам нужно использовать 64-разрядные соглашения о вызовах. В этом случае номер системного вызова переходит в %rax, а аргументы - в %rdi, %rsi и %rdx

редактировать

лучшее место, которое я нашел для этого, это www.x86-64.org, в частности abi.формат PDF


64-разрядные процессоры могут запускать 32-разрядный код, но для этого они должны использовать специальный режим. Все эти инструкции действительны в 64-разрядном режиме, поэтому ничто не помешало вам создать 64-разрядный исполняемый файл.

ваш код строит и работает правильно с gcc -m32 -nostdlib hello.S. Это потому что -m32 определяет __i386, Так что /usr/include/asm/unistd.h включает в себя <asm/unistd_32.h>, который имеет правильные константы для int x80 ABI.

см. также сборка 32-разрядных двоичных файлов в 64-разрядной системе (GNU toolchain) подробнее о _start и main С / без libc и статических и динамических исполняемых файлов.

$ file a.out 
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=973fd6a0b7fa15b2d95420c7a96e454641c31b24, not stripped

$ strace ./a.out  > /dev/null
execve("./a.out", ["./a.out"], 0x7ffd43582110 /* 64 vars */) = 0
strace: [ Process PID=2773 runs in 32 bit mode. ]
write(1, "hello wolrd\n", 12)           = 12
exit(0)                                 = ?
+++ exited with 0 +++

технически, если бы вы использовали правильные номера вызовов, ваш код также работал бы в 64-битном режиме:что произойдет, если вы используете 32-разрядный int 0x80 Linux ABI в 64-разрядном коде? но int 0x80 не рекомендуется в 64-битный код. (На самом деле, это никогда не рекомендуется. Для эффективности 32-разрядный код должен вызывать через экспортированное ядро VDSO страница, чтобы он мог использовать sysenter для быстрых системных вызовов процессоров, которые поддерживают его).


но это не отвечает на мои вопросы. Что именно!--56-- > is происходит в этом случае?

хороший вопрос.

В Linux int x80 С eax=1 и sys_exit(ebx), независимо от того, в каком режиме вызывающего процесса в. 32-разрядный ABI доступен в 64-разрядном режиме (если ваше ядро не было скомпилировано без i386 ABI поддержка), но не используйте его. Ваш статус выхода из movl $(STDOUT), %ebx.

(кстати, есть STDOUT_FILENO макрос определен в unistd.h, но не #include <unistd.h> С .S потому что он также содержит прототипы C, которые не являются допустимым синтаксисом asm.)

обратите внимание, что __NR_exit С unistd_32.h и __NR_write С unistd_64.h как 1, так как первый int x80 завершает свой процесс. Вы используете неправильные номера системных вызовов для ABI, вы вызывающий.


strace расшифровывает его неправильно, как будто ты вызвал syscall (потому что это ABI, который, как ожидается, будет использовать 64-битный процесс). каковы соглашения о вызовах для системных вызовов UNIX и Linux на x86-64

eax=1 / syscall означает write(rd=edi, buf=rsi, len=rdx) и strace неправильно декодировать int x80.

rdi и rsi are 0 (он же NULL) на въезде в _start, и ваш код устанавливает rdx=12 С movl $(helloend-hellostr) , %edx.

Linux инициализирует регистры до нуля в новом процессе после execve. (ABI говорит undefined, Linux выбирает ноль, чтобы избежать утечки информации). В статически слинкованный исполняемый файл, _start - это первый пользовательский космический код, который запускается. (В динамическом исполняемом файле динамический компоновщик запускается до _start, и оставляет мусор в регистрах).

см. также x86 tag wiki для получения дополнительных ссылок asm.