Scraper Обновлено: 3 October, 2019

SPO600 - Лаборатория ассемблера, часть 1

  Перевод   Ссылка на автора

Это первый раз, когда я сталкиваюсь с языком ассемблера. Это выглядит пугающим для меня в начале. Это также первый раз, когда я использовал Makefile, чтобы упростить компиляцию.

Версии C «Hello World» для x86_64

Профессор предоставил нам некоторые коды на C и на ассемблере для изучения. Все они достигают одной цели: напечатать «Hello World». Три C-программы вызывают разные функции в main (). Функции перечислены ниже:

1. printf("Hello World!\n");
2. write(1,"Hello World!\n",13);
3. syscall(__NR_write,1,"Hello World!\n",13);

Машинный код и
Раздел

С помощьюobjdump -dкоманда распечатать машинный код и разобрать его в ассемблер для каждого из двоичных файлов и проверить

раздел:

Для версии «printf»:

0000000000401126 <main>:  401126: 55                    push   %rbp  401127: 48 89 e5              mov    %rsp,%rbp  40112a: bf 10 20 40 00        mov    $0x402010,%edi  40112f: b8 00 00 00 00        mov    $0x0,%eax  401134: e8 f7 fe ff ff        callq  401030 <printf@plt>  401139: b8 00 00 00 00        mov    $0x0,%eax  40113e: 5d                    pop    %rbp  40113f: c3                    retq

Для версии «написать»:

0000000000401126 <main>:  401126: 55                    push   %rbp  401127: 48 89 e5              mov    %rsp,%rbp  40112a: ba 0d 00 00 00        mov    $0xd,%edx  40112f: be 10 20 40 00        mov    $0x402010,%esi  401134: bf 01 00 00 00        mov    $0x1,%edi  401139: e8 f2 fe ff ff        callq  401030 <write@plt>  40113e: b8 00 00 00 00        mov    $0x0,%eax  401143: 5d                    pop    %rbp  401144: c3                    retq  401145: 66 2e 0f 1f 84 00 00  nopw   %cs:0x0(%rax,%rax,1)  40114c: 00 00 00  40114f: 90                    nop

Для версии «syscall»:

0000000000401126 <main>:  401126: 55                    push   %rbp  401127: 48 89 e5              mov    %rsp,%rbp  40112a: b9 0d 00 00 00        mov    $0xd,%ecx  40112f: ba 10 20 40 00        mov    $0x402010,%edx  401134: be 01 00 00 00        mov    $0x1,%esi  401139: bf 01 00 00 00        mov    $0x1,%edi  40113e: b8 00 00 00 00        mov    $0x0,%eax  401143: e8 e8 fe ff ff        callq  401030 <syscall@plt>  401148: b8 00 00 00 00        mov    $0x0,%eax  40114d: 5d                    pop    %rbp  40114e: c3                    retq  40114f: 90                    nop

Заметил, что версия «printf» имеет наименьшее количество похвал, тогда как версия «syscall» самая длинная.

Ассемблер с использованием синтаксиса NASM и GAS

Обе программы выводят «Hello World» после компиляции и запуска. Использование objdump -d для просмотра объектных файлов:

Версия NASM:

0000000000000000 <_start>:  0: ba 0e 00 00 00        mov    $0xe,%edx  5: 48 b9 00 00 00 00 00  movabs $0x0,%rcx  c: 00 00 00  f: bb 01 00 00 00        mov    $0x1,%ebx  14: b8 04 00 00 00        mov    $0x4,%eax  19: cd 80                 int    $0x80  1b: b8 01 00 00 00        mov    $0x1,%eax  20: cd 80                 int    $0x80

Версия ГАЗ:

0000000000000000 <_start>:  0: 48 c7 c2 0e 00 00 00  mov    $0xe,%rdx  7: 48 c7 c6 00 00 00 00  mov    $0x0,%rsi  e: 48 c7 c7 01 00 00 00  mov    $0x1,%rdi  15: 48 c7 c0 01 00 00 00  mov    $0x1,%rax  1c: 0f 05                 syscall  1e: 48 c7 c7 00 00 00 00  mov    $0x0,%rdi  25: 48 c7 c0 3c 00 00 00  mov    $0x3c,%rax  2c: 0f 05                 syscall

Оба они намного короче, чем скомпилированные объектные файлы Си. Также интересно, что оба они после компиляции выглядят очень похоже на исходные коды.

Версии C на aarch64

Использование objdump -d для проверки объектных файлов, и результаты показаны ниже:

Для версии «printf»:

0000000000400594 <main>:  400594: a9bf7bfd  stp x29, x30, [sp, #-16]!  400598: 910003fd  mov x29, sp  40059c: 90000000  adrp x0, 400000 <_init-0x418>  4005a0: 9119c000  add x0, x0, #0x670  4005a4: 97ffffb7  bl 400480 <printf@plt>  4005a8: 52800000  mov w0, #0x0                    // #0  4005ac: a8c17bfd  ldp x29, x30, [sp], #16  4005b0: d65f03c0  ret  4005b4: 00000000  .inst 0x00000000 ; undefined

Для версии «написать»:

0000000000400594 <main>:  400594: a9bf7bfd  stp x29, x30, [sp, #-16]!  400598: 910003fd  mov x29, sp  40059c: d28001a2  mov x2, #0xd                    // #13  4005a0: 90000000  adrp x0, 400000 <_init-0x418>  4005a4: 9119e001  add x1, x0, #0x678  4005a8: 52800020  mov w0, #0x1                    // #1  4005ac: 97ffffb1  bl 400470 <write@plt>  4005b0: 52800000  mov w0, #0x0                    // #0  4005b4: a8c17bfd  ldp x29, x30, [sp], #16  4005b8: d65f03c0  ret  4005bc: 00000000  .inst 0x00000000 ; undefined

Для версии «syscall»:

0000000000400594 <main>:  400594: a9bf7bfd  stp x29, x30, [sp, #-16]!  400598: 910003fd  mov x29, sp  40059c: 528001a3  mov w3, #0xd                    // #13  4005a0: 90000000  adrp x0, 400000 <_init-0x418>  4005a4: 9119e002  add x2, x0, #0x678  4005a8: 52800021  mov w1, #0x1                    // #1  4005ac: d2800800  mov x0, #0x40                   // #64  4005b0: 97ffffb4  bl 400480 <syscall@plt>  4005b4: 52800000  mov w0, #0x0                    // #0  4005b8: a8c17bfd  ldp x29, x30, [sp], #16  4005bc: d65f03c0  ret

Результат очень похож на x86_64. Версия «printf» имеет самый короткий код, а версия «syscall» - самая длинная. Шаги очень похожи с x86_64.

Assmebler на aarch64

Как и ожидалось, после компиляции и запуска ассемблерного кода в aarch64 содержимое объектного файла очень похоже на исходный код.

0000000000000000 <_start>:  0: d2800020  mov x0, #0x1                    // #1  4: 10000001  adr x1, 0 <_start>  8: d28001c2  mov x2, #0xe                    // #14  c: d2800808  mov x8, #0x40                   // #64  10: d4000001  svc #0x0  14: d2800000  mov x0, #0x0                    // #0  18: d2800ba8  mov x8, #0x5d                   // #93  1c: d4000001  svc #0x0

Цикл 0–9 на x86_64

Следующая задача - создать цикл, который выводит следующий результат:

Loop: 0
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
Loop: 6
Loop: 7
Loop: 8
Loop: 9

Код, который достиг результата выше, показан ниже. Подробное значение для каждой строки кода объясняется в комментариях.

.text.globl    _startstart = 0                       /* starting value for the loop index; note that this is a symbol (constant), not a variable */max = 10                        /* loop exits when the index hits this number (loop condition is i<max) */_start:   mov     $start,%r15         /* loop index */loop:/* ... body of the loop ... do something useful here ... */   mov     $len,%rdx   mov     $48,%r14            /*initialize number as 48(in ASCII)*/   add     %r15,%r14           /*add the index number to number above */   movb    %r14b,msg+6         /*store the binary value at the sixth  byte location of msg*/   mov     $msg,%rsi   mov     $1,%rdi  /*set stdout*/   mov     $1,%rax  /*syscall sys_write */syscall   inc     %r15                /* increment index */   cmp     $max,%r15           /* see if we're done */   jne     loop                /* loop if we're not */   mov     $0,%rdi             /* exit status */   mov     $60,%rax            /* syscall sys_exit */syscall.datamsg: .ascii "Loop:  \n"   len = . - msg

Цикл 0-9 на aarch64

Код, который выполняет тот же вывод, что и выше, в системе aarch64 показан ниже:

.text.globl _start_start:    mov     x20,0           /* initialize loop index to 0*/loop:    mov     x2, len         /* message length (bytes) */    mov     x21,48          /* initialize first number to 48(AICII)*/    add     x21,x21,x20    adr     x1, msg    /* message location (memory address) */    strb    w21,[x1,len-3]  /* store number to x1 register*/    mov     x0, 1           /* file descriptor: 1 is stdout */    mov     x2, len    /* message length (bytes) */    mov     x8, 64      /* write is syscall #64 */    svc     0           /* invoke syscall */
add x20,x20,1 cmp x20,10 /*check whether reach max*/ b.ne loop mov x0, 0 /* status -> 0 */ mov x8, 93 /* exit is syscall #93 */ svc 0 /* invoke syscall */.datamsg: .ascii "Loop: \n"len= . - msg

Мой опыт работы с ассемблером до сих пор

Изучив Python и Javascript, я почувствовал, что язык C довольно сложный и строгий с точки зрения синтаксиса. Тем не менее, эта лаборатория дала мне понять, что C по-прежнему относительно простой язык по сравнению с языком ассемблера. Синтаксис для ассемблера довольно сложный, и рекомендации также различны для разных систем. Я думаю, что мое чувство могло бы быть еще более драматичным, если бы я попробовал язык ассемблера на большем количестве платформ.

Кроме того, в лаборатории я впервые играю с компьютерными регистрами, что делает язык ассемблера действительно мощным. Он может манипулировать регистрами побайтно. Этот опыт абсолютно открыл мой разум о том, как и при каких обстоятельствах мы используем ассемблер в реальном мире. Я определенно сделаю некоторые исследования в ближайшее время.

Сравнивая программирование на серверах x86_64 и aarch64, оба они выглядят довольно схожими. Я лично нашел синтаксис для x86_64 проще для понимания. Некоторые рекомендации, такие как strb, str, ldrb, ldr, немного запутывают для использования в aarch64. В целом, для меня это был забавный и потрясающий опыт.