目录 Table of Contents
汇编编程示例
处理命令行参数的示例
汇编程序启动的时候, 你可以在命令行里面给它传入参数, 现在我们看看怎么在汇编里处理这个参数.
.text
.globl _start
_start:
popl %ecx # argc
vnext:
popl %ecx # argv
test %ecx, %ecx # 空指针表明结束
jz exit
movl %ecx, %ebx
xorl %edx, %edx
strlen:
movb (%ebx), %al
inc %edx
inc %ebx
test %al, %al
jnz strlen
movb $10, -1(%ebx) # $10 为换行键
movl $4, %eax # 系统调用号 (sys_write)
movl $1, %ebx # 文件描述符 (stdout)
int $0x80
jmp vnext
exit:
movl $1, %eax # 系统调用号 (sys_exit
xorl %ebx, %ebx # 退出代码
int $0x80
以下为代码详细注释
popl %ecx # argc
, 我们从这个注释就可以看出来, 当一个程序通过命令行启动, 传输给它的参数 (argc, argv) 还是保存在栈中. 从命令行来看, 它的传输也还是从右往左压栈. 所以这第一个参数最后压栈, 最先出栈. 然后我就知道 ecx 里面存着当前有多少参数的数值.
如果你 C 程序写熟了的话, int main 第一个参数就是 int argc, 表示你这 main 函数里面一共有多少个参数.
然后我们进入一个循环 vnext
, 继续 popl. 现在我知道有多少个参数了, 然后我再 pop 下一个. 下一个 argv 是什么意思呢, 我们再回忆下 C 里面的 main 函数.
C 里面 main 函数还有个参数就是 char **argv
, 实际上里面就指向了一系列的参数, 指向了一个参数表, 就是每个参数在里面都用一个字符串的形式来存储的, 我们就指向它这个地址.
test %ecx, %ecx
jz exit
然后如果是 ecx 是个空指针 null, 就结束, 说明我们没有其他的参数.
退出的话呢, 就用系统调用 sys_exit. ebx 退出代码就是 0 了, 自己和自己进行异或嘛, 就退出了.
如果不是空指针的话, 那就一行行来.
实际上就是 movl %ecx, %ebx
, 然后 xorl %edx, %edx
edx 清零.
ecx 里面存储着参数列表当前的起始地址, 这个地址给放到 ebx 里面去
放进去之后, 我们就一个一个地把这个字符串参数, 把它里面的每个字节一个个都给它取出来, 因为命令行参数最终都转换成一个字符串的形式, 然后取出来, 就是 movb (%ebx), %al
ebx 取第一个放到 al 里面去
inc %edx
, edx 相当于计数, 看你当前字符串参数, 里面有多少 byte
inc %ebx
, 相当于指针往后面挪了一下
test %al, %al
, 如果 al 为 0, 就说明当前字符串参数就结束了
如果不结束的话, 继续循环
现在结束了, 我们相当与把当前 argv 的长度计算出来了, 计算出来的长度值实际上放到了 edx 里面了
然后在里面添加一个换行符 jnz strlen
movb $10, -1(%ebx)
, 现在字符串参数结束了, 我就把 $10 挪过去放到你字符串的尾巴上, $10 正好是换行符
然后我通过 write 系统调用, 在标准输出上面, 把你的命令行参数打出来, 打出来之后再跳回去
我们这就把它一个个的命令行参数给一个个地打出来
调用 lib_c 库函数
这个程序能够打印出你当前使用的处理器生产厂商它的 id 是什么.
.section .data
output:
.asciz "The processor vendor id is '%s'\n" # 这里需要说明, 我们用的不是 .ascii 而是 .asciz, 就是说这还是字符串, 但是我默认在你字符串后面加一个 0. 因为我们后面调用 printf, C 语言里面字符串默认找 0 作为结尾.
.section .bss # 可读可写并且没有初始化的数据段.
# 实际上数据段里面分成各种各样的类型, .bss 是说它是一个全局的数据段, 大家各个模块都可见, 但是这个全局数据段是没有经过初始化的. 这种初始化和未初始化的数据段是分开的, 稍后解答为什么区分开.
.lcomm buffer,12 # 在这个数据段里声明一个长度为 12 的 buffer
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
mov $buffer, %edi # buffer 就是上面我们指明的数据段的地址, 注意它前面有 $ 符号, 就相当于把这个地址本身放到 edi 里面去.
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi) # 我们把生成的这三个信息, 挨个存到 edi 指向的那块内存区域里面去
push $buffer # printf 规格化的一个字符串
push $output # 转义字符序列
call printf # 调用 printf
addl $8, %esp # 因为是调用 C 库函数, 所以遵循 C 的调用约定
push $0
call exit # 调用 libc 里面的 exit 函数
cpuid
是一条汇编指令, 它是请求处理器的特定信息, 并且把信息返回到特定寄存器的低级指令.它是用单一寄存器值作为输入, 实际上就是 %eax 寄存器, %eax 用于指明
cpuid
到底要生成什么信息. 取回来的信息根据 %eax 的值, 在 %ebx, %ecx, %edx 寄存器中存放关于处理器的不同信息.当前我们要取 CPU 生产厂家的 id, 那就是填充 0, 之后就把厂商 id 字符串返回到 %ebx %edx %ecx 寄存器中, 每个里面自低向高存放 4 个字节 :
1) %ebx 包含字符串的最低 4 个字节
2) %edx 包含字符串的中间 4 个字节
3) %ecx 包含字符串的最高 4 个字节
as -o cpuid.o cpuid.s --32
ld -lc -dynamic-linker /lib/ld-linux.so.2 -o cpuid cpuid.o -m elf_i386
因为我们调用的 libc 函数, .lc 就是去连接 libc 库, 我们采用这种动态链接的方式, 生成一个执行文件