是的,所有这些都是汇编语言。请注意,汇编语言是由工具而不是目标定义的。因此 risc-v 的 gnu 汇编器(gas)可能与 risc-v 的其他一些汇编器不同。大多数差异将出现在其他东西上,除了指令之外的东西,但有时指令也会从一种工具更改为另一种工具。唯一不变的是机器代码,如果汇编器可以生成正确的指令,汇编语言很容易看起来像这样
add banana, orange
但不管怎么说。
unsigned int fun ( unsigned int a, unsigned int b )
{
return(a+b+7);
}
我目前为此目标使用的 gcc 编译器正在从该代码生成它。
.file "so.c"
.option nopic
.attribute arch, "rv32i2p0_m2p0_a2p0_f2p0_d2p0_c2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 1
.globl fun
.type fun, @function
fun:
addi a1,a1,7
add a0,a1,a0
ret
.size fun, .-fun
.ident "GCC: (GNU) 10.2.0"
有些事情有些明显,有些事情不是那么明显,实际上很少需要为这个目标和工具制作一个有用的对象
.globl fun
fun:
addi a1,a1,7
add a0,a1,a0
ret
fun: 只是一个标签,表示地址。链接器在将对象组合在一起以创建可用程序时,会整理标签并根据需要将它们转换为固定或相对的地址。这是一个省力的设备。否则
00000000 <skip-0x8>:
0: c501 beqz x10,8 <skip>
2: 0001 nop
4: 0001 nop
6: 0001 nop
00000008 <skip>:
8: 00c58533 add x10,x11,x12
00000000 <skip-0xc>:
0: c511 beqz x10,c <skip>
2: 0001 nop
4: 0001 nop
6: 0001 nop
8: 0001 nop
a: 0001 nop
0000000c <skip>:
c: 00c58533 add x10,x11,x12
您可以自己查找的指令编码,基本上包括到目的地的距离,没有标签我们将不得不计算我们自己的指令数量,并且在汇编语言中以某种方式指示向前跳 3 个半字,向后跳 10 个半字字。
Gnu 汇编器对此有一个语法。
nop
nop
nop
beqz x10,.+4
nop
nop
nop
nop
nop
nop
00000000 <.text>:
0: 0001 nop
2: 0001 nop
4: 0001 nop
6: c111 beqz x10,a <.text+0xa>
8: 0001 nop
a: 0001 nop
c: 0001 nop
e: 0001 nop
10: 0001 nop
12: 0001 nop
但是您通常不希望这样做,就好像您更改分支指令和目标之间的指令/字节数一样,您必须不断调整一些/许多偏移量。
所以标签是地址,在 gnu 汇编器中它们以冒号结尾并且不使用保留字。一些汇编语言不使用冒号。
细分市场:
int mybss;
int mydata=5;
int text ( void )
{
mybss=3;
return(++mydata);
}
Disassembly of section .text:
00000000 <text>:
0: 000007b7 lui x15,0x0
4: 0007a503 lw x10,0(x15) # 0 <text>
8: 00000737 lui x14,0x0
c: 468d li x13,3
e: 0505 addi x10,x10,1
10: 00d72023 sw x13,0(x14) # 0 <text>
14: 00a7a023 sw x10,0(x15)
18: 8082 ret
Disassembly of section .sbss:
00000000 <mybss>:
0: 0000 unimp
...
Disassembly of section .sdata:
00000000 <mydata>:
0: 0005 c.nop 1
...
嗯,这很有趣,那是 gnu gcc 创建的。反正。传统上,无论出于何种原因,您都可以使用 Google 搜索。代码(基本上是指令)位于名为 .text 的段中。预初始化数据为.data,未初始化数据为.bss。嗯gnu用了名字、文本、数据、bss前面的点。
Gnu 汇编器有一些快捷方式
.text
nop
.data
.word 1,2,3
但完整的语法是
.section .text
.section .data
你可以随心所欲,我假设如果它没有以某种方式保留:
.section .hello
nop
add x11,x12,x13
j .
.word 0xA,0xBBBB
.section .world
mystuff: .word 1,2,3,4
Disassembly of section .hello:
00000000 <.hello>:
0: 0001 nop
2: 00d605b3 add x11,x12,x13
6: a001 j 6 <.hello+0x6>
8: 000a c.slli x0,0x2
a: 0000 unimp
c: 0000bbbb 0xbbbb
Disassembly of section .world:
00000000 <mystuff>:
0: 0001 nop
2: 0000 unimp
4: 0002 c.slli64 x0
6: 0000 unimp
8: 00000003 lb x0,0(x0) # 0 <mystuff>
c: 0004 0x4
...
这些是对象转储,请注意每个段如何拥有自己的数据块,因为此输出从偏移量零开始。另请注意,这是反汇编程序,因此它试图将数据反汇编为令人困惑的指令。
这里的想法是您隔离这些不同的数据/信息类型,以便您可以控制它们的去向。例如,在微控制器中,您可能在一个地址空间 0x00000000 处有闪存,而在另一个地址空间 0x20000000 处可能有 sram,因此您希望将只读代码和只读数据与读/写数据隔离开来,以便您可以告诉链接器在哪里放东西。
int mybss;
int mydata=5;
const int myrodata = 25;
int text ( void )
{
mybss=3;
return(++mydata);
}
您可以在链接器命令行上或使用特定于工具、链接器而不是目标通用的链接器脚本将点连接到链接器。与汇编语言一样,不期望代码将逐字移植到其他工具链。
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.srodata*) } > bob
.data : { *(.sdata*) } > ted
.bss : { *(.sbss*) } > ted
}
显然不是一个实际可用的二进制文件,但这些工具不知道他们只是按照我的要求做了
Disassembly of section .text:
00000000 <text>:
0: 200007b7 lui x15,0x20000
4: 0007a503 lw x10,0(x15) # 20000000 <mydata>
8: 20000737 lui x14,0x20000
c: 468d li x13,3
e: 0505 addi x10,x10,1
10: 00d72223 sw x13,4(x14) # 20000004 <mybss>
14: 00a7a023 sw x10,0(x15)
18: 8082 ret
Disassembly of section .rodata:
0000001c <myrodata>:
1c: 0019 c.nop 6
...
Disassembly of section .data:
20000000 <mydata>:
20000000: 0005
...
Disassembly of section .bss:
20000004 <mybss>:
20000004: 0000
...
在我有 bob 和 ted 的地方,大多数人会使用 rom 和 ram 或其他更有用的名称。在左边我有 .text 和 .data 的地方,你也可以在那里制作东西,需要与将读取此二进制文件的工具匹配,并查找该外部工具想要查看的某些关键字。但是使用 gnu 链接器,您可以弥补这些。中间的名称虽然需要匹配对象中的名称,所以我不知道为什么它是 .sdata 而不是 .data,这对我来说是今天的新名称,但无论哪种方式,我都只是查看对象并匹配链接描述文件中的内容,然后控制它们的去向。
最后是 main:又是一个标签,你已经看到编译函数的函数名基本上是一个地址,它表示为一个标签。该函数/子例程的入口点。您的 C 程序没有进入 main(),在运行的二进制文件中的 main 之前运行引导代码,然后该代码调用 main。当您使用时gcc hello_world.c -o hello_world
,有预处理、编译为汇编、汇编为对象并使用默认链接器和 C 库引导程序链接以生成目标(操作系统)特定的二进制文件,以便您可以运行它./hello_world
。
您遇到的代码可能已经打算(即使您发布的内容不起作用)以这种方式链接和运行。或者作者只是习惯于使用 main 这个词,因为我们必须编写 C 程序。即使编译为汇编语言 C 代码,就工具而言,它只是另一个标签。C 的引导程序将专门对其进行外部调用,因此当所有内容都链接时,其中一个对象中需要有一个 main(),但是您可以构建没有名为 main() 的函数的二进制文件,并且如果您掌握了它就可以工作工具。至少对于gnu。其他工具可能出于某种原因需要该函数名称。