2024-12-18

vak: (Default)
Удалось мне наконец с помощью bintrace оттрассировать выполнение простейшей программы на маке с процессором arm-64. Конкретно мак мини M1, но на M2/M3/M4 то же самое будет.

Как выяснилось, в MacOS на новых процессорах ARM невозможно собрать юзерный бинарник статически. То есть линкер-то можно вызвать с флагом -static, и даже выполняемый файл получится, но при запуске он упадёт с диагностикой "Killed: 9".

Так что всё теперь через ж... динамический загрузчик. А он штука серьёзная. Файл трассировки занял, на минуточку, 472 мегабайта. Выполнено 7838857 машинных команд загрузчика и 9 команд программы юзера. Вот она, расплата за гибкость. 😀

Вот такой исходник на ассемблере:
#include <sys/syscall.h>
.text
.align 2
.globl _main
_main:
mov x16, #SYS_write // syscall write(fd, message, size)
mov x0, #1 // file descriptor 1 - stdout
adrp x1, message@PAGE // high 20 bits of message address
add x1, x1, :lo12:message@PAGEOFF // low 12 bits of message address
mov x2, 13 // size
svc 0x80 // do system call

mov x16, #SYS_exit // syscall exit(status)
mov x0, #0 // status - 0
svc 0x80 // do system call
message:
.ascii "Hello world!\n"
Начало трассы:
Starting program: ./demo-arm64-macos
sp = 0x16fdffaf8
cpsr = 0x1000
0x000000010000cb30: 910003e0 mov x0, sp
x0 = 0x16fdffaf8
0x000000010000cb34: 927cec1f and sp, x0, #0xfffffffffffffff0
sp = 0x16fdffaf0
0x000000010000cb38: d280001d mov x29, #0
0x000000010000cb3c: d280001e mov x30, #0
0x000000010000cb40: 14000307 b #0x10000d75c
0x000000010000d75c: d503237f pacibsp
0x000000010000d760: a9bb6ffc stp x28, x27, [sp, #-0x50]!
sp = 0x16fdffaa0
Вот конец трассы. Цветом выделен код пользователя:
0x00000001971b0258:  f94027e8   ldr x8, [sp, #0x48]
x8 = 0x100003f74
0x00000001971b025c: f94033e9 ldr x9, [sp, #0x60]
x9 = 0x1fc3b04d0
0x00000001971b0260: f9400529 ldr x9, [x9, #8]
x9 = 0x1fc3b0150
0x00000001971b0264: b9406920 ldr w0, [x9, #0x68]
x0 = 0x1
0x00000001971b0268: a9470921 ldp x1, x2, [x9, #0x70]
x1 = 0x16fdffb08
x2 = 0x16fdffb18
0x00000001971b026c: f9404123 ldr x3, [x9, #0x80]
x3 = 0x16fdffb98
0x00000001971b0270: d63f091f blraaz x8
lr = 0x1971b0274
0x0000000100003f74: d2800090 mov x16, #4
x16 = 0x4
0x0000000100003f78: d2800020 mov x0, #1
0x0000000100003f7c: 90000001 adrp x1, #0x100003000

x1 = 0x100003000
0x0000000100003f80: 913e6021 add x1, x1, #0xf98
x1 = 0x100003f98
0x0000000100003f84: d28001a2 mov x2, #0xd
x2 = 0xd
0x0000000100003f88: d4001001 svc #0x80
Hello world!
x0 = 0xd
x1 = 0
cpsr = 0x40001000
0x0000000100003f8c: d2800030 mov x16, #1
x16 = 0x1
0x0000000100003f90: d2800000 mov x0, #0
x0 = 0
0x0000000100003f94: d4001001 svc #0x80
Process exited normally.
vak: (Daemon)
Подрихтовал ещё немного тулзу, и таки добился своего. Можем теперь наблюдать интимные подробности работы динамического загрузчика.

Пример состоит из двух частей, обе написаны на ассемблере.
  • Главная программа foobar.S выдаёт пару текстовых сообщений, вызывая функцию say() из динамической библиотеки. Затем завершается системным вызовом _exit().
  • Бибилиотека lib.S реализует функцию say(), которая выполняет системный вызов write().
Всё это на процессоре arm32 под FreeBSD.



Компилируем, запускаем. Работу динамического загрузчика я сократил для ясности. Инструкции главной программы выделены зелёным, инструкции библиотечной функции сиреневым. Видно, как при первом вызове функции say() её адрес ещё не готов. Активируется динамический загрузчик, находит адрес функции и уходит в неё. При втором вызове say() переход происходит напрямую. Вставка-переходник say@plt занимает три машинных команды.
$ cpp foobar.S | as -o foobar.o -
$ cpp lib.S | as -o lib.o -
$ ld -shared -o lib.so lib.o
$ ld -o foobar foobar.o lib.so
$ export LD_LIBRARY_PATH=.
$ bintrace ./foobar
Starting program: ./foobar
        sp = 0xbfbfeb50
        lr = 0x20025538
      cpsr = 0x10
----------------------------------------------------------- dynamic loader
0x20025538:  e1a0600d   mov r6, sp
        r6 = 0xbfbfeb50
...
(initialize dynamic loader)
...
0x20025590:  e1a0f003   mov pc, r3
----------------------------------------------------------- 00010158 <_start>:
0x00010158:  e300017c   movw r0, #0x17c
        r0 = 0x17c
0x0001015c:  e3400001   movt r0, #1
        r0 = 0x1017c
0x00010160:  ebfffff9   bl #0x1014c
        lr = 0x10164
----------------------------------------------------------- 0001014c <say@plt>:
0x0001014c:  e28fc600   add ip, pc, #0, #12
       r12 = 0x10154
0x00010150:  e28cca01   add ip, ip, #0x1000
       r12 = 0x11154
0x00010154:  e5bcf0c4   ldr pc, [ip, #0xc4]!
       r12 = 0x11218
----------------------------------------------------------- 00010138 <.plt>:
0x00010138:  e52de004   str lr, [sp, #-4]!
        sp = 0xbfbfeb4c
0x0001013c:  e59fe004   ldr lr, [pc, #4]
        lr = 0x10c4
0x00010140:  e08fe00e   add lr, pc, lr
        lr = 0x1120c
0x00010144:  e5bef008   ldr pc, [lr, #8]!
        lr = 0x11214
----------------------------------------------------------- into dynamic loader
0x200255a0:  e92d0c3f   push {r0, r1, r2, r3, r4, r5, sl, fp}
        sp = 0xbfbfeb2c
...
(find symbol 'say' in lib.so)
...
0x200255d4:  e1a0f00c   mov pc, ip
----------------------------------------------------------- 000000d4 <say>:
0x200170d4:  e92d4080   push {r7, lr}
        sp = 0xbfbfeb48
0x200170d8:  e1a01000   mov r1, r0
        r1 = 0x1017c
0x200170dc:  e3a00001   mov r0, #1
        r0 = 0x1
0x200170e0:  e3a02004   mov r2, #4
        r2 = 0x4
0x200170e4:  e3a07004   mov r7, #4
        r7 = 0x4
0x200170e8:  ef000000   svc #0
foo
        r0 = 0x4
        r1 = 0
0x200170ec:  e8bd8080   pop {r7, pc}
        r7 = 0
        sp = 0xbfbfeb50
----------------------------------------------------------- back to _start
0x00010164:  e3000180   movw r0, #0x180
        r0 = 0x180
0x00010168:  e3400001   movt r0, #1
        r0 = 0x10180
0x0001016c:  ebfffff6   bl #0x1014c
        lr = 0x10170
----------------------------------------------------------- 0001014c <say@plt>:
0x0001014c:  e28fc600   add ip, pc, #0, #12
       r12 = 0x10154
0x00010150:  e28cca01   add ip, ip, #0x1000
       r12 = 0x11154
0x00010154:  e5bcf0c4   ldr pc, [ip, #0xc4]!
       r12 = 0x11218
----------------------------------------------------------- 000000d4 <say>:
0x200170d4:  e92d4080   push {r7, lr}
        sp = 0xbfbfeb48
0x200170d8:  e1a01000   mov r1, r0
        r1 = 0x10180
0x200170dc:  e3a00001   mov r0, #1
        r0 = 0x1
0x200170e0:  e3a02004   mov r2, #4
0x200170e4:  e3a07004   mov r7, #4
        r7 = 0x4
0x200170e8:  ef000000   svc #0
bar
        r0 = 0x4
        r1 = 0
0x200170ec:  e8bd8080   pop {r7, pc}
        r7 = 0
        sp = 0xbfbfeb50
----------------------------------------------------------- back to _start
0x00010170:  e3a07001   mov r7, #1
        r7 = 0x1
0x00010174:  e3a00000   mov r0, #0
        r0 = 0
0x00010178:  ef000000   svc #0
Process exited normally.