9.gas汇编工具:as(at&t风格)语法说明
使用$标识立即数
|
再寄存器前面加上%
|
源操作数在前,目标操作数在后
|
使用$获取变量地址
|
长跳转使用:ljmp $section, $offset
|
一个简单的汇编语言程序框架:
.section .data
……
.section .bss
……
.section .text
.globl _start
_start:
……
范例:
#cpuid2.s View the CPUID Vendor ID string using C library calls
.section .datatext
output:
.asciz "The processor Vendor ID is '%s'\n"
.section .bss
.lcomm buffer, 12
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $buffer, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $buffer
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
伪指令说明:
data
|
.ascii
|
定义字符串,没有\0结束标记
|
data
|
.asciz
|
有\0结束标记
|
data
|
.byte
|
字节
|
data
|
.int
|
32位
|
data
|
.long
|
32位
|
data
|
.shot
|
16位
|
bss
|
.lcomm
|
对于上面的例子是声明12字节的缓冲区,l标识local,仅当前汇编程序可用
|
bss
|
.comm
|
通用内存区域
|
data/text
|
.equ
|
.equ LINUX_SYS_CALL, 0x80
movl $ LINUX_SYS_CALL, %eax
说明:equ不是宏而是常量,会占据数据/代码段空间
|
指令集说明:
|
movb/movw/movl
|
|
|
cmov
|
根据cf, of, pf, zf等标识位判断并mov
|
|
xchg
|
操作时会lock内存,非常耗费cpu时间
|
|
bswap
|
翻转寄存器中字节序
|
|
xadd
|
|
|
pushx, popx
|
|
|
pushad, popad
|
|
|
jmp
|
|
|
call
|
|
|
cmp
|
|
|
jz/jb/jne/jge
|
|
|
loop
|
|
|
addb/addw/addl
|
|
|
subb/subw/subl
|
|
|
dec/inc
|
|
|
mulb/muw/mull
无符号乘法
|
源操作数长度
|
目标操作数
|
目标位置
|
8位
|
al
|
ax
|
16位
|
ax
|
dx:ax
|
32位
|
eax
|
edx:eax
|
|
imul有符合乘法
|
|
|
divb/divw/divl
无符合除法
(被除数在eax中,除数在指令中给出)
|
被除数
|
被除数长
|
商
|
余数
|
ax
|
16位
|
al
|
ah
|
dx:ax
|
32位
|
ax
|
dx
|
edx:eax
|
64位
|
eax
|
edx
|
|
idiv有符合除法
|
|
|
sal/shl/sar/shr
|
移位
|
|
rol/ror/rcl/rcr
|
循环移位
|
|
leal
|
取地址:leal output, %eax
等同于:movl $output, %eax
|
|
rep
|
rep movsb 执行ecx次
|
|
lodsb/lodsw/lodsl
stosb/stosw/stosl
|
取存内存中的数据
|
|
|
|
|
|
|
|
|
gas程序范例(函数调用):
文件1:area.s定义函数area
# area.s - The areacircumference function
.section .text
.type area, @function
.globl area
area:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
fldpi
filds 8(%ebp)
fmul %st(0), %st(0)
fmulp %st(0), %st(1)
fstps -4(%ebp)
movl -4(%ebp), %eax
movl %ebp, %esp
popl %ebp
ret
文件2:functest4.s调用者
# functest4.s - An example of using external functions
.section .data
precision:
.byte 0x7f, 0x00
.section .bss
.lcomm result, 4
.section .text
.globl _start
_start:
nop
finit
fldcw precision
pushl $10
call area
addl $4, %esp
movl %eax, result
pushl $2
call area
addl $4, %esp
movl %eax, result
pushl $120
call area
addl $4, %esp
movl %eax, result
movl $1, %eax
movl $0, %ebx
int $0x80
10.读连接器和加载器的一些笔记,感谢原作者colyli at gmail dot com,看了他翻译的lnl及写的一个os,受益匪浅。
如果不是很深入的研究连接器和加载器的话,了解一些原理就足够了。举个例子说明吧:
1 #include
2 #include
3 #include
4 #include
5
6 int a = 1;
7 int main()
8 {
9 printf("value: %d\n", a);
10
11 return 0;
12 }
编译指令:gcc -c hello.c -o hello.o 汇编
gcc -o hello hello.o 编译
objdump -d hello.o 反汇编目标文件
objdump -d hello 反汇编可执行文件
比较两端结果:
objdump -d hello.o
|
objdump -d hello
|
00000000 :
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 83 e4 f0 and $0xfffffff0,%esp
9: b8 00 00 00 00 mov $0x0,%eax
e: 83 c0 0f add $0xf,%eax
11: 83 c0 0f add $0xf,%eax
14: c1 e8 04 shr $0x4,%eax
17: c1 e0 04 shl $0x4,%eax
1a: 29 c4 sub %eax,%esp
1c: 83 ec 08 sub $0x8,%esp
1f: ff 35 00 00 00 00 pushl 0x0
25: 68 00 00 00 00 push $0x0
2a: e8 fc ff ff ff call 2b
2f: 83 c4 10 add $0x10,%esp
32: b8 00 00 00 00 mov $0x0,%eax
37: c9 leave
38: c3 ret
|
08048368 :
8048368: 55 push %ebp
8048369: 89 e5 mov %esp,%ebp
804836b: 83 ec 08 sub $0x8,%esp
804836e: 83 e4 f0 and $0xfffffff0,%esp
8048371: b8 00 00 00 00 mov $0x0,%eax
8048376: 83 c0 0f add $0xf,%eax
8048379: 83 c0 0f add $0xf,%eax
804837c: c1 e8 04 shr $0x4,%eax
804837f: c1 e0 04 shl $0x4,%eax
8048382: 29 c4 sub %eax,%esp
8048384: 83 ec 08 sub $0x8,%esp
8048387: ff 35 94 95 04 08 pushl 0x8049594
804838d: 68 84 84 04 08 push $0x8048484
8048392: e8 19 ff ff ff call 80482b0
8048397: 83 c4 10 add $0x10,%esp
804839a: b8 00 00 00 00 mov $0x0,%eax
804839f: c9 leave
80483a0: c3 ret
80483a1: 90 nop
80483a2: 90 nop
80483a3: 90 nop
|
简单说明:由于程序运行时访问内存,执行跳转都需要确切的地址。所以汇编处理的目标文件里面没有包含,而是把这个工作放到连接器中:即定位地址。
当程序需要动态链接到某个库上时,使用该库的got表动态定位跳转即可。
具体可以看colyli大侠的《链接器和加载器Beta 2》,及《从程序员角度看ELF》
11.连接器脚本ld—script(相关内容来自《GLD中文手册》)
ld --verbose查看默认链接脚本
ld把一定量的目标文件跟档案文件连接起来,并重定位它们的数据,连接符号引用.一般在编译一个程序时,最后一步就是运行ld。
实例1:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
注释:“.”是定位计数器,设置当前节的地址。
实例2:
floating_point = 0;
SECTIONS
{
. = ALIGN(4);
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
. = ALIGN(4);
_bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
注释:定义一个符合_etext,地址为.text结束的地方,注意源程序中不能在此定义该符合,否则链接器会提示重定义,而是应该象下面这样使用:
extern char _etext;
但是可以在源程序中使用etext符合,连接器不导出它到目标文件。
实例3:
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
这个例子是一个完整的连接脚本。它告诉连接器去读取文件all.o中的所有节,并把它们放到输出节outputa的开始位置处, 该输出节是从位置0x10000处开始的。从文件foo.o中来的所有节.input1在同一个输出节中紧密排列。 从文件foo.o中来的所有节.input2全部放入到输出节outputb中,后面跟上从foo1.o中来的节.input1。来自所有文件的所有余下的.input1和.input2节被写入到输出节outputc中。
示例4:连接器填充法则:
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } } 错误
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } } 正确
示例5:VMA和LMA不同的情况
SECTIONS
{
.text 0x1000 : { *(.text) _etext = . ; }
.mdata 0x2000 :
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
程序:
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
*dst++ = *src++;
}
/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0;
示例6:linux-2.6.14/arch/i386/kernel $ vi vmlinux.lds.S
linux内核的链接脚本,自行分析吧,有点复杂哦。