从gcc输出理解汇编语言

4

我正在学习计算机基础课程,尝试理解汇编代码,并在C语言中创建了一个"hello world "程序并编译为汇编代码。我知道"mov r0,r3"将数据从寄存器3移动到寄存器0。但是,我该如何确定r3的值是多少呢?

以下是我正在使用的汇编代码:

.arch armv6
.eabi_attribute 27, 3
.eabi_attribute 28, 1
.fpu vfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file   "hello.c"
.section    .rodata
.align  2
.LC0:
    .ascii  "Hello World\000"
    .text
    .align  2
    .global main
    .type   main, %function
main:
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 1, uses_anonymous_args = 0
    stmfd   sp!, {fp, lr}
    add     fp, sp, #4
    ldr     r0, .L2
    bl      printf
    mov     r0, r3
    ldmfd   sp!, {fp, pc}
.L3:
    .align  2
.L2:
    .word   .LC0
    .size   main, .-main
    .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
    .section    .note.GNU-stack,"",%progbits

以下是我的C代码:
// Hello World program in C

#include<stdio.h>

main()
{
    printf("Hello World");
}

2
当你看到像 x=y 这样的 C 语句时,你如何确定 y 的值?这里也是同样的道理。 - n. m.
4
godbolt给出了一个完全一样的输出,但是多了一行解释了r3的用法。 - Margaret Bloom
1
@MargaretBloom - 所以在你的godbolt示例中,r3具有从main()返回的返回值,并且被放入r0中。没有显式返回的main()具有隐含的return 0。但在OP的示例中,不能保证r3包含0 - 也许他正在使用一个早于C99的编译器,因此它只返回任意值? - Michael Burr
1
你必须写一个main和printf吗?其他函数可能更容易理解,比如无符号整数fun(无符号整数a,无符号整数b){return(a + b);} 你还需要了解参数传递的调用约定,即参数是通过寄存器传递的。 - old_timer
1
你没有明确返回任何内容,因此编译器似乎可以随意制造任何垃圾返回... - old_timer
显示剩余5条评论
2个回答

3
使用GNU调试器"gdb"! 输入gdb --args ./store01启动 GNU 调试器。它应该像控制台一样工作。键入quit退出。
当然你想要逐步运行程序并检查寄存器内容,所以键入start跳转到main()并跳过所有初始化操作。 然后键入disassembledisas来显示汇编代码。
现在通过键入 stepi 按指令步进运行程序。
现在是有趣的部分:键入info registers r3并查看输出!
另外一个神奇的事情是:你可以在运行时更改变量的值:尝试 p $r0 = 2
这还不是全部。以下是官方文档:https://www.gnu.org/software/gdb/documentation/ 。这是一个好用的、简短且有用的教程:http://thinkingeek.com/2013/01/12/arm-assembler-raspberry-pi-chapter-4

1

好的,让我们来分解一下。

stmfd sp!, {fp, lr}

STMFD是“STore Multiple Full Descending”的缩写。本质上,它使用寄存器sp作为降序堆栈指针,并按顺序将寄存器fplr放置在堆栈中。fp是帧指针,lr是链接寄存器(保存返回地址,稍后很重要)。

add fp, sp, #4

将帧指针寄存器设置为等于sp + 4(指向刚刚存储fp的位置)。这是没有进行优化编译的产物。

ldr r0, .L2

这是加载存储在地址.L2处的32位值(在这种情况下是.LC0的地址,即字符串(或字符数组)"Hello World!\000")到寄存器r0的简写。

bl printf

通过调用以空字符结尾的字符串"Hello World"上的printf函数组装。请记住,指向该数组的指针刚刚被加载到r0中。一般来说,GCC使用寄存器r0-r3作为第一个、第二个、第三个和第四个参数传递给函数。必要时,其他参数将按需存储在堆栈上。 mov r0, r3 这表明您的代码存在错误,这个版本的GCC没有捕获到。基本上,您的函数返回一种类型(假定为int,因为您从未另行指定),但您从未指定返回值。因此,返回值是r3中的任何内容,这是未定义的。 ldmfd sp!,{fp,pc} 这是函数开头编译的stmfd指令的反向操作,但是我们将其加载到fp, pc而非fp, lr中。这会导致lr的原始值直接移动到程序计数器pc中。pc是一个特殊的寄存器,它指向下一个要执行的指令。每当pc被设置为一个值时,CPU立即开始执行该值所指向的代码。由于ARM指令集没有明确的ret操作,因此利用这个属性来return到调用例程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接