汇编语言中的global _start是什么?

41

这是我的汇编语言代码...

section .text
global _start
_start: mov eax, 4
        mov ebx, 1
        mov ecx, mesg
        mov edx, size
        int 0x80
exit:   mov eax, 1
        int 0x80
section .data
mesg    db      'KingKong',0xa
size    equ     $-mesg

输出:

root@bt:~/Arena# nasm -f elf a.asm -o a.o
root@bt:~/Arena# ld -o out a.o
root@bt:~/Arena# ./out 
KingKong

我的问题是全局_start变量的作用是什么?我尝试通过谷歌先生来寻找答案,发现它被用来告诉程序的起始点。那么为什么我们不能像下面这个代码一样只使用 _start 来指定程序开始的位置并在屏幕上显示警告呢?

section .text
_start: mov eax, 4
        mov ebx, 1
        mov ecx, mesg
        mov edx, size
        int 0x80
exit:   mov eax, 1
        int 0x80
section .data
mesg    db      'KingKong',0xa
size    equ     $-mesg

root@bt:~/Arena# nasm -f elf a.asm
root@bt:~/Arena# ld -e _start -o out a.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000008048080
root@bt:~/Arena# ld -o out a.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000008048080

1
可能是"global main" in Assembly的重复问题。 - Jens Björnhager
4个回答

58

global 指令是 NASM 特有的。它用于将代码中的符号导出到生成的目标代码中指向的位置。在这里,您将 _start 符号标记为全局,因此它的名称将添加到目标代码 (a.o) 中。连接器 (ld) 可以从目标代码中读取该符号及其值,从而知道在输出可执行文件中将其标记为入口点的位置。当您运行可执行文件时,它将从代码中标记为 _start 的位置开始执行。

如果一个符号缺少 global 指令,则该符号将不会被放置在目标代码的导出表中,因此链接器无法了解该符号。

如果您想要使用除默认值 _start 之外的其他入口点名称,可以向 ld 指定 -e 参数,如下所示:

ld -e my_entry_point -o output_filename object_filename

7
"_start" 是 ld 默认知道的一个入口点。使用命令 "ld -o out a.o -e _main" 即可起作用。 - Frank Kotler
@ssg,非常感谢您的回答。我可以清楚地理解您的观点,即我可以将_start更改为_main或任何我想要的名称。但是,我不明白为什么要使用“global _start”这一行?为什么链接器不能在我的程序中搜索_start并设置其作为执行的起始点?为什么要使用该指令“global”? - vikkyhacks
3
原因是,除非您明确添加“global”指令,否则在目标代码中不会放置任何符号。否则,它们都被视为局部指令,并在汇编过程中被丢弃。 - Sedat Kapanoglu
2
我建议在你的示例中使用 ld -e my_entry_point。这通常不适用于由C编译器生成的main(),因此使用_main只会让那些还不理解所有组件如何配合的人感到困惑。 - Peter Cordes

10

_start被默认的Binutils的ld链接脚本用作入口点。

我们可以通过以下方式查看该链接脚本的相关部分:

 ld -verbose a.o | grep ENTRY

输出结果为:

ENTRY(_start)

ELF文件格式(以及其他目标文件格式),通过e_entry头字段明确指定程序将从哪个地址开始运行。

ENTRY(_start)告诉链接器在从目标文件生成ELF文件时将该入口设置为符号_start的地址。

然后,当操作系统启动程序(在Linux上使用exec系统调用)时,它解析ELF文件,将可执行代码加载到内存中,并将指令指针设置为指定的地址。

Sedat提到的-e标志覆盖了默认的_start符号。

你可以使用-T <script>选项替换整个默认的链接脚本,这里有一个具体的示例,设置了一些裸机汇编内容.global是一个汇编指令,用于标记ELF文件中的符号为全局变量。
ELF文件包含每个符号的一些元数据,指示其可见性。
最简单的观察方法是使用nm工具。
例如,在Linux x86_64 GAS freestanding hello world中:

main.S

.text
.global _start
_start:
asm_main_after_prologue:
    /* write */
    mov $1, %rax   /* syscall number */
    mov $1, %rdi   /* stdout */
    lea msg(%rip), %rsi  /* buffer */
    mov $len, %rdx /* len */
    syscall

    /* exit */
    mov $60, %rax   /* syscall number */
    mov $0, %rdi    /* exit status */
    syscall
msg:
    .ascii "hello\n"
    len = . - msg

GitHub 上游

编译并运行:

gcc -ffreestanding -static -nostdlib -o main.out main.S
./main.out

nm 的输出结果为:

00000000006000ac T __bss_start
00000000006000ac T _edata
00000000006000b0 T _end
0000000000400078 T _start
0000000000400078 t asm_main_after_prologue
0000000000000006 a len
00000000004000a6 t msg

man nm 告诉我们:

如果是小写字母,该符号通常是局部的;如果是大写字母,则该符号是全局的(外部的)。

因此我们可以看到,_global 可以在外部访问(大写字母 T),但我们没有标记为 .globalmsg 不能访问(小写字母 t)。

链接器 知道如果看到多个具有相同名称的全局符号时如何崩溃, 或者 在看到更多异类符号类型时执行更智能的操作

如果我们不将 _start 标记为全局,ld 就会变得悲伤并显示:

无法找到入口符号 _start


ld -o a.o 只是表示“没有输入文件”。您想要什么命令行选项? - Peter Cordes

5
在你声明它为全局变量之前并不是显式的全局变量,因此您必须使用全局指令来声明。链接器需要全局标签,如果没有全局地址,链接器将抱怨找不到。 您没有将声明为全局变量,因此它在该模块/对象代码之外不可见,因此对链接器也不可见。 这与C相反,C中的内容默认为全局变量,除非您将其声明为本地变量。
unsigned int hello;
int fun ( int a )
{
  return(a+1);
}

你好和乐趣是全局的,它们在对象外可见,但这个<p>标签没有完整的句子或上下文,因此无法进行翻译。
static unsigned int hello;
static int fun ( int a )
{
  return(a+1);
}

使它们本地化并不可见。

全部本地化:

_start:
hello:
fun:
more_fun:

现在这些内容已经全局可用于链接器和其他对象中了。

global _start
_start:
global hello
hello:
...

1
请注意,这些指令是特定于汇编器的,即将汇编语言转换为机器码的程序。汇编语言通常没有标准,因此每个汇编器都可以做自己的事情,例如“intel格式”与“at&t格式”是相同指令集的极端示例。同样,有些人可能需要“global”,而其他人则可能需要“.global”。因此,您正在学习工具链的细微差别,而不一定是指令集。 - old_timer
这真的让人很难理解。我们在C语言中有局部变量和全局变量的概念,因为我们使用了函数。汇编语言中是否也有作用域呢?(如果我说错了,请纠正我,我刚开始学习汇编)。为什么链接器不能在我的程序中搜索_start并将其设置为执行的起点?它缺少什么信息来完成这个任务? - vikkyhacks
3
我猜你可以把汇编中的标签视为C语言上下文中的“静态”符号,至少默认情况下是这样。也就是说,在文件/翻译单元范围内才能使用它们。使用“.global”定义一个标签会使汇编器导出它(将其添加到目标文件的符号表中),以便链接器稍后可以找到它,供其他翻译单元使用(或在你的程序启动时使用)。 - Carl Norum
在C语言中,局部变量和全局变量是相对于上下文的,特别是由单个C文件(或函数)创建的对象。与汇编语言没有什么不同,标签/变量是相对于上下文的,即由该单个源文件创建的对象。 - old_timer
1
就像在C语言或其他语言中一样,链接器仅“搜索”已定义为全局的标签/变量/函数的目标。这与源语言无关,对于该特定语言,您必须将该标签/变量/函数等定义为对象、上下文或全局,以供所有人使用(对象外部)。 - old_timer
链接器可以搜索那个对象并找到它,但那将违反链接器的工作。 最好的情况是,链接器可能会给出一个错误“_start找到但不是全局的”或类似的内容。 ld是开源软件,您可以添加这样的功能。 - old_timer

1

global _start 只是一个指向内存地址的标签。在 ELF 二进制文件中,对于 _start 标签来说,它是默认使用的标签,作为程序启动的地址。

还有 main_mainmain_,这些都是 C 语言中使用的函数名,由“启动代码”调用,如果你在使用 C 语言的话,“启动代码”通常会链接到这些函数。

希望这能帮到你。


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