Это первый раз, когда я сталкиваюсь с языком ассемблера. Это выглядит пугающим для меня в начале. Это также первый раз, когда я использовал Makefile, чтобы упростить компиляцию.
Профессор предоставил нам некоторые коды на 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» самая длинная.
Обе программы выводят «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
Оба они намного короче, чем скомпилированные объектные файлы Си. Также интересно, что оба они после компиляции выглядят очень похоже на исходные коды.
Использование 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.
Как и ожидалось, после компиляции и запуска ассемблерного кода в 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
Следующая задача - создать цикл, который выводит следующий результат:
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
Код, который выполняет тот же вывод, что и выше, в системе 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. В целом, для меня это был забавный и потрясающий опыт.