linux - servidores - Ensamblar binarios de 32 bits en un sistema de 64 bits(cadena de herramientas GNU)
herramientas que utiliza linux (2)
Estoy aprendiendo el ensamblaje x86 (en Ubuntu 18.04 de 64 bits) y tuve un problema similar con el mismo ejemplo (es de Programación desde cero , en el capítulo 4 [ http://savannah.nongnu.org/projects/pgubook/ ]).
Después de hurgar, encontré las siguientes dos líneas ensambladas y unidas:
as power.s -o power.o --32
ld power.o -o power -m elf_i386
Estos le dicen a la computadora que solo está trabajando en 32 bits (a pesar de la arquitectura de 64 bits).
Si desea utilizar la
gdb debugging
, use la línea de ensamblador:
as --gstabs power.s -o power.o --32.
El .code32 parece ser innecesario.
No lo he intentado a tu manera, pero el ensamblador de GNU (gas) también parece estar bien con:
inicio .globl
# (es decir, no ''a'' en global).
Además, sugeriría que probablemente desee mantener los comentarios del código original, ya que parece que se recomienda hacer comentarios profundos en el ensamblaje. (Incluso si usted es el único que mira el código, le resultará más fácil darse cuenta de lo que estaba haciendo si lo mira meses o años después).
Sin embargo, sería bueno saber cómo alterar esto para usar los registros
RSP
64-bit R*X
y
RBP
.
Escribo el código de ensamblaje que se puede compilar:
as power.s -o power.o
hay un problema cuando enlace el archivo de objeto power.o:
ld power.o -o power
Para ejecutar en el sistema operativo de 64 bits (Ubuntu 14.04), agregué
.code32
al comienzo del archivo
power.s
, sin embargo, todavía recibo un error:
Falla de segmentación (núcleo descargado)
power.s
:
.code32
.section .data
.section .text
.global _start
_start:
pushl $3
pushl $2
call power
addl $8, %esp
pushl %eax
pushl $2
pushl $5
call power
addl $8, %esp
popl %ebx
addl %eax, %ebx
movl $1, %eax
int $0x80
.type power, @function
power:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %ebx
movl 12(%ebp), %ecx
movl %ebx, -4(%ebp)
power_loop_start:
cmpl $1, %ecx
je end_power
movl -4(%ebp), %eax
imull %ebx, %eax
movl %eax, -4(%ebp)
decl %ecx
jmp power_loop_start
end_power:
movl -4(%ebp), %eax
movl %ebp, %esp
popl %ebp
ret
TL: DR
: use
gcc -m32
.
.code32
no
cambia el formato del archivo de salida, y eso es lo que determina el modo en que se ejecutará su programa.
.code32
de usted no intentar ejecutar el código de 32 bits en el modo de 64 bits.
.code32
es para ensamblar código de máquina "extraño" que pueda desear como datos, o para exportar en un segmento de memoria compartida.
Si eso no es lo que está haciendo, evítelo para que obtenga errores de tiempo de compilación cuando construya un
.S
en el modo incorrecto si tiene alguna instrucción
push
o
pop
, por ejemplo.
Sugerencia: use la extensión
.S
para ensamblador escrito a mano.
(
gcc foo.S
lo ejecutará a través del preprocesador C antes
as
, por lo que puede #incluir un encabezado con números de syscall, por ejemplo).
Además, lo distingue de la salida del compilador
.s
(de
gcc foo.c -O3 -S
).
Para construir binarios de 32 bits, use uno de estos comandos
gcc -g foo.S -o foo -m32 -nostdlib -static # static binary with absolutely no libraries or startup code
# -nostdlib by itself makes static executables on Linux, but not OS X.
gcc -g foo.S -o foo -m32 # dynamic binary including the startup boilerplate code. Use with code that defines a main() but not a _start
Documentación para
nostdlib
,
-nostartfiles
y
-static
.
Usando funciones libc de
_start
(vea el final de esta respuesta para ver un ejemplo)
Algunas funciones, como
malloc(3)
o funciones stdio, incluida
printf(3)
, dependen de algunos datos globales que se inicializan (por ejemplo,
FILE *stdout
y el objeto al que realmente apunta).
gcc -nostartfiles
el código CRT
_start
repetitivo, pero aún vincula
libc
(dinámicamente, por defecto).
En Linux, las bibliotecas compartidas pueden tener secciones de inicializador que ejecuta el vinculador dinámico cuando las carga, antes de saltar a su punto de entrada
_start
.
Entonces
gcc -nostartfiles hello.S
todavía te permite llamar a
printf
.
Para un ejecutable dinámico, el núcleo ejecuta
/lib/ld-linux.so.2
en él en lugar de ejecutarlo directamente (use
readelf -a
para ver la cadena "ELF interpreter" en su binario).
Cuando su
_start
finalmente se ejecuta, no todos los registros se pondrán a cero, porque el vinculador dinámico ejecutó el código en su proceso.
Sin embargo,
gcc -nostartfiles -static hello.S
se vinculará, pero se bloqueará en tiempo de ejecución
si llama a
printf
o algo sin llamar a las funciones de inicio internas de glibc.
(Ver el comentario de Michael Petch).
Por supuesto, puede colocar cualquier combinación de
.c
,
.S
y
.o
en la misma línea de comando para vincularlos a todos en un solo ejecutable.
Si tiene alguna C, no olvide
-Og -Wall -Wextra
: no desea depurar su asm cuando el problema era algo simple en la C que lo llama de lo que el compilador podría haberle advertido.
Use
-v
para que gcc le muestre los comandos que ejecuta para ensamblar y vincular.
Para hacerlo "manualmente"
:
as foo.S -o foo.o -g --32 && # skips the preprocessor
ld -o foo foo.o -m elf_i386
file foo
foo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
gcc -nostdlib -m32
es más fácil de recordar y escribir que las dos opciones diferentes para as y ld (
--32
y
-m elf_i386
).
Además, funciona en todas las plataformas, incluidas aquellas en las que el formato ejecutable no es ELF.
(
Pero los ejemplos de Linux no funcionarán en OS X, porque los números de llamada del sistema son diferentes
, o en Windows porque ni siquiera usa el
int 0x80
ABI).
NASM / YASM
gcc no puede manejar la sintaxis NASM.
(
-masm=intel
se parece más a la sintaxis MASM que a la NASM, donde necesita un
offset symbol
para obtener la dirección de forma inmediata).
Y, por supuesto, las directivas son diferentes (por ejemplo
.globl
vs
global
).
Puede construir con
nasm
o
yasm
, luego vincular
.o
con
gcc
como se
gcc
arriba, o
ld
directamente.
Utilizo un script de envoltura para evitar la escritura repetitiva del mismo nombre de archivo con tres extensiones diferentes.
(nasm y yasm predeterminan a
file.asm
->
file.o
, a diferencia de GNU como salida predeterminada de
a.out
).
Use esto con
-m32
para ensamblar y vincular ejecutables ELF de 32 bits.
No todos los sistemas operativos usan ELF, por lo que este script es menos portátil que usar
gcc -nostdlib -m32
para enlazar.
#!/bin/sh
# usage: asm-link [-q] [-m32] foo.asm [assembler options ...]
# Just use a Makefile for anything non-trivial. This script is intentionally minimal and doesn''t handle multiple source files
verbose=1 # defaults
fmt=-felf64
#ldopt=-melf_i386
while getopts ''m:vq'' opt; do
case "$opt" in
m) if [ "m$OPTARG" = "m32" ]; then
fmt=-felf32
ldopt=-melf_i386
fi
if [ "m$OPTARG" = "mx32" ]; then
fmt=-felfx32
ldopt=-melf32_x86_64
fi
# default is -m64
;;
q) verbose=0 ;;
v) verbose=1 ;;
esac
done
shift "$((OPTIND-1))" # Shift off the options and optional --
src=$1
base=${src%.*}
shift
[ "$verbose" = 1 ] && set -x # print commands as they''re run, like make
#yasm "$fmt" -Worphan-labels -gdwarf2 "$src" "$@" &&
nasm "$fmt" -Worphan-labels -g -Fdwarf "$src" "$@" &&
ld $ldopt -o "$base" "$base.o"
# yasm -gdwarf2 includes even .local labels so they show up in objdump output
# nasm defaults to that behaviour of including even .local labels
# nasm defaults to STABS debugging format, but -g is not the default
Prefiero yasm por algunas razones, incluyendo que por defecto hace largos
nop
s en lugar de rellenar con muchos
nop
solo byte.
Eso hace que la salida de desmontaje sea desordenada, además de ser más lenta si los nops alguna vez se ejecutan.
(En NASM, debe usar el paquete de macro
smartalign
).
Ejemplo: un programa que utiliza funciones libc de _start
# hello32.S
#include <asm/unistd_32.h> // syscall numbers. only #defines, no C declarations left after CPP to cause asm syntax errors
.text
#.global main # uncomment these to let this code work as _start, or as main called by glibc _start
#main:
#.weak _start
.global _start
_start:
mov $__NR_gettimeofday, %eax # make a syscall that we can see in strace output so we know when we get here
int $0x80
push %esp
push $print_fmt
call printf
#xor %ebx,%ebx # _exit(0)
#mov $__NR_exit_group, %eax # same as glibc''s _exit(2) wrapper
#int $0x80 # won''t flush the stdio buffer
movl $0, (%esp) # reuse the stack slots we set up for printf, instead of popping
call exit # exit(3) does an fflush and other cleanup
#add $8, %esp # pop the space reserved by the two pushes
#ret # only works in main, not _start
.section .rodata
print_fmt: .asciz "Hello, World!/n%%esp at startup = %#lx/n"
$ gcc -m32 -nostdlib hello32.S
/tmp/ccHNGx24.o: In function `_start'':
(.text+0x7): undefined reference to `printf''
...
$ gcc -m32 hello32.S
/tmp/ccQ4SOR8.o: In function `_start'':
(.text+0x0): multiple definition of `_start''
...
Falla en tiempo de ejecución, porque nada llama a las funciones de inicio glibc.
(
__libc_init_first
,
__dl_tls_setup
y
__libc_csu_init
en ese orden, según el comentario de Michael Petch. Existen otras implementaciones de
libc
, incluido
MUSL
que está diseñado para enlaces estáticos y funciona sin llamadas de inicialización).
$ gcc -m32 -nostartfiles -static hello32.S # fails at run-time
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=ef4b74b1c29618d89ad60dbc6f9517d7cdec3236, not stripped
$ strace -s128 ./a.out
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29681 runs in 32 bit mode. ]
gettimeofday(NULL, NULL) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
También puede
gdb ./a.out
y ejecutar
b _start
,
layout reg
,
run
y ver qué sucede.
$ gcc -m32 -nostartfiles hello32.S # Correct command line
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=7b0a731f9b24a77bee41c13ec562ba2a459d91c7, not stripped
$ ./a.out
Hello, World!
%esp at startup = 0xffdf7460
$ ltrace -s128 ./a.out > /dev/null
printf("Hello, World!/n%%esp at startup = %#lx/n", 0xff937510) = 43 # note the different address: Address-space layout randomization at work
exit(0 <no return ...>
+++ exited (status 0) +++
$ strace -s128 ./a.out > /dev/null # redirect stdout so we don''t see a mix of normal output and trace output
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29729 runs in 32 bit mode. ]
brk(0) = 0x834e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
.... more syscalls from dynamic linker code
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
mmap2(NULL, 1814236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfffffffff7556000 # map the executable text section of the library
... more stuff
# end of dynamic linker''s code, finally jumps to our _start
gettimeofday({1461874556, 431117}, NULL) = 0
fstat64(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 # stdio is figuring out whether stdout is a terminal or not
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0xff938870) = -1 ENOTTY (Inappropriate ioctl for device)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7743000 # 4k buffer for stdout
write(1, "Hello, World!/n%esp at startup = 0xff938fb0/n", 43) = 43
exit_group(0) = ?
+++ exited with 0 +++
Si hubiéramos usado
_exit(0)
, o si el sistema
sys_exit
nos llamara con
int 0x80
,
la
write(2)
no habría sucedido
.
Con stdout redirigido a un no-tty, el valor predeterminado es de búfer completo (no de búfer de línea), por lo que la
write(2)
solo se activa mediante
fflush(3)
como parte de la
exit(3)
.
Sin redirección, llamar a
printf(3)
con una cadena que contenga nuevas líneas se vaciará inmediatamente.
Puede ser deseable comportarse de manera diferente dependiendo de si stdout es un terminal, pero solo si lo hace a propósito, no por error.