qqCPUID 的一个汇编实例
2010年09月04日
使用 CPUID 查看 CPUID 生成的厂商 ID 字符串,目的是了解汇编程序编写的基本框架,编译,连接,调试。程序代码:
#cpuid.s Sample program to extract the processor Vendor ID
.section.data
output:
.ascii "The processor Vendor ID is 'xxxxxxxxxxxx'\n"
.section.text
.global _start
_start:
movl$2,%eax
cpuid
movl $output,%edi
movl %ebx, 28(%edi)
movl %edx, 32(%edi)
movl %ecx, 36(%edi)
movl $4, %eax
movl $1, %ebx
movl $output,%ecx
movl $42, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
, 因为 gcc 超找的标签是 main ,而 as 查找的是 _start 。改了之后,可以如下编译并生成可执行文件:
使用 gdb 来调试程序
使用 gdb 调试汇编程序,首先必须使用 -gstabs 参数重新编译汇编源代码:
需要注意的是,调试完程序后,要重新用不使用 -gstabs 参数的方式再编译生成程序。因为有了 -gstabs 参数,程序里会添加进调试信息,整个程序会变得庞大,如上面的程序使用 -gstabs 和不使用 -gstabs 选项所生成的程序大小差别为:
可见,用的比不用的差不多比不用的大了它的 1/2 大小。假如程序越大,那么产生的调试信息也就越多。
经过 -gstabs 编译后,现在可以用 gdb 进行调试了:
beyes@beyes-groad:~/programming/assembly/cpuid$ gdb cpuid
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/beyes/programming/assembly/cpuid/cpuid...don e.
(gdb)
),然后再使用 run 命令,但会看到程序不会在断点处停下,而是直接运行完整个程序。可以说,这是一个长期的缺陷,也可能不算是一个缺陷。解决这个问题的办法是,在 _start 标签后面加一条 NOP 指令,如:
后面,就可以使用 next 或者 step 来跟踪调试。
要查看相关的数据内容,经常会使用到以下 3 个命令:
数据命令
描述
info registers
显示所有寄存器的值
显示特定寄存器或来自程序的变量的值
x
显示特定内存位置的内容
(gdb) info registers
eax 0xa 10
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff480 0xbffff480
ebp 0x0 0x0
esi 0x0 0
edi 0x80490ac 134516908
eip 0x8048081 0x8048081
eflags 0x212 [ AF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0
print 命令也可以用于显示各个寄存器的值,还可以在 print 后加一个修饰符就可以修改 print 命令输出格式:
x 命令用于显示特定内存位置的值。和 print 命令类似,可以使用修饰符修改 x 命令的输出。x 命令的格式是:
其中,n 是要显示的字段数,y 是输出格式,它可以是:
(gdb) x/42cb &output
0x80490ac : 84 'T' 104 'h' 101 'e' 32 ' ' 112 'p' 114 'r' 111 'o' 99 'c'
0x80490b4 : 101 'e' 115 's' 115 's' 111 'o' 114 'r' 32 ' ' 86 'V' 101 'e'
0x80490bc : 110 'n' 100 'd' 111 'o' 114 'r' 32 ' ' 73 'I' 68 'D' 32 ' '
0x80490c4 : 105 'i' 115 's' 32 ' ' 39 '\'' 71 'G' 101 'e' 110 'n' 117 'u'
0x80490cc : 105 'i' 110 'n' 101 'e' 73 'I' 110 'n' 116 't' 101 'e' 108 'l'
0x80490d4 : 39 '\'' 10 '\n'
上面,42 表示显示 output 变量 (&符号用于表明它是一个内存位置) 的前 42 个字节;c 表示一次显字符;b 表示一次显示一个字节。在输出的内容里,字符前面的数字是这个字符的 ASCII 码。当跟踪对内存位置进行操作的指令时,这个命令的特性价值无法衡量。
movl $0, %eax
给 eax 送入 0 值,目的在于这个输入可以使 cpuid 输出厂商 ID 字符串。
cpuid
执行 CPUID 指令。
执行 cpuid 指令后,就要用 3 个输出寄存器 ebx, edx, ecx 来收集指令的相应信息:
movl $output, %edi
movl %ebx, 28(%edi)
movl %edx, 32(%edi)
movl %ecx, 36(%edi)
上面的28, 32, 36 对应的是 "The processor Vendor ID is 'xxxxxxxxxxxx'\n" 中的 'xxxxxxxxxxxx 的偏移位置。
movl $output, %edi 是创建了一个指针 edi ,output 标签的内存位置被加载到 EDI 中。这里需要注意,EBX, EDX, ECX 的顺序,这个顺序并不是按照 B,C,D 来排序,比较奇怪。
经过上面的几条指令后,就在内存中放置好厂商的 ID 字符串了,下面的几条指令用来显示这些信息:
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $42, %edx
int $0x80
第一条指令,把 4 放到 eax 寄存器中。这个 4 表示系统调用值。在较新的内核中,这些系统调用定义在:
arch/x86/include/asm/unistd_32.h
文件中。比如 4 号系统调用是:
这里,每个系统调用都被定义为一个名称 (前面加上 __NR_) ,和它的系统调用号。
第二条指令,把 1 放到 ebx 寄存器中。这里的 1 是文件描述符,表示的标准输出 (STDOUT) .
所以,第 1 ,第 2 条指令合起来的意思是,调用 write() 系统调用,往标准输出上输出要显示的内容。
第三条指令,是字符串的开头送往 ecx 中。
第四条指令,是把字符串的长度送往 edx 中。
对比 write() 函数的原型:
write(int fd, const void *buf, size_t count);
从左到右,3 个参数分别对应着 ebx, ecx, edx 这 3 个寄存器中的值。可见:
EAX 包含系统调用值
EBX 包含要写入的文件描述符
ECX 包含字符串的开头
EDX 包含字符串的长度
最后,在显示了厂商的 ID 信息之后,就干净的退出程序。同样,Linux 系统调用可以为此提供帮助,这时使用系统 1 号调用 (#define __NR_exit 1) ,程序被正确的终止,并且返回到命令提示符。在使用 exit() 时,exit() 也有一个作为退出代码的参数,这个参数装载 ebx 寄存器中。
Linux 内核提供了许多可以很容易地从汇编应用程序访问的预置函数(如系统调用),为了访问这些内核函数,必须使用 int 指令码,它生成具有 0x80 值的软件中断。执行的具体函数由 EAX 寄存器中的值来确定。如果没有这个内核函数,就必须自己把每个输出字符发送到正确的显示器 I/O 地址,这无疑要花费很多的时间。
在汇编中使用 C 库函数,对程序开发达到简洁省时的目的,代码如下:
#cpuid2.s View the CPUID Vendor ID string using C library calls
.section.data
output:
.asciz"The processor Vendor ID is '%s'\n"
.section.bss
.lcommbuffer,12
.section.text
.global_start
_start:
movl$0,%eax
cpuid
movl$buffer,%edi
movl%ebx, (%edi)
movl%edx,4(%edi)
movl%ecx,8(%edi)
pushl$buffer
pushl$output
callprintf
addl$8,%esp
pushl$0
callexit
在程序中,使用了 C 库中函数 printf 。如果直接编译连接,那么编译通过,而连接时则会出错:
为了连接 C 库,首先要确保这个 C 库在系统上可用。把 C 函数连接到汇编程序有两种方法。
第一种是静态连接 ( static linking ) 。静态连接把函数的目标代码直接连接到应用程序的可执行程序文件中,这样创建的可执行文件会很大;而且,如果同时运行程序的多个实例,就会造成内存浪费( 每个实例都有其自己的相同函数的拷贝 )。
第二种是动态连接 ( dynamic linking ) 。动态连接使用库的方式在程序运行时,由操作系统调用动态链接库,并且多个程序可以共享动态链接库。标准的 C 动态库位于 libc.so.x 文件中。我的系统是 ubuntu 9.10,所使用的是 libc.so.6 版本,其实这是一个软连接,指向真实的库名是 libc-2.10.1.so 。
如果程序使用 gcc 来编译(在程序中 _start 改成 main),则文件会自动连接到 C 程序。
在用 ld 连接目标文件时,为了连接 libc.so 文件,必须使用 -l 参数,在指定库时,并不需要指定完整的库名称:
上面,-l 选项后面紧接着一个 c 。c 表示 /lib/libc ,c 的前面部分 /lib/lib 是所有函数动态库的默认命名前缀。
经过上面的连接后,生成了可执行文件,但这个可执行文件却不能正确执行:
由上可见,现在可以正常执行程序了。
使用 gcc 编译器时,会自动连接必须的 C 库,无须进行任何特殊的操作,但 gcc 是静态连接;而在 ld 里指定 -dynamic-linker 参数则是动态连接,动态连接是在运行时加载,所以最后产生的可执行文件比较小,如:
或者用 objdump -d cpuid2 命令来查看 dump 出的文件信息,也可以明显看到 gcc 编译生成的文件里有很多内容。
程序说明:
调用 printf 函数,事先要把 printf() 函数参数压入堆栈,参数入栈的顺序是从右到左。
发表评论
-
动静库
2012-01-20 12:18 724动静库 2010年12月16日 ... -
Gcc简易教程
2012-01-20 12:17 681Gcc简易教程 2010年06月13日 版权属于GodT ... -
编译程序与操作系统的关系
2012-01-20 12:17 861编译程序与操作系统的 ... -
strcpy没有声明 c++头文件详解
2012-01-20 12:17 2344strcpy没有声明 c++头文件详解 2010年10月23 ... -
python 常用类库!(转)
2012-01-19 17:00 668python 常用类库!(转) 2011年01月21日 ... -
前言:什么是Python?
2012-01-19 16:59 706前言:什么是Python? 2010年11月14日 py ... -
对比java和python
2012-01-19 16:59 1196对比java和python 2011年04 ... -
(转载)Python 应用发布技术
2012-01-19 16:59 722(转载)Python 应用发布技 ... -
Python 应用领域
2012-01-19 16:59 780Python 应用领域 2010年08 ... -
最真的爱(转)
2012-01-17 06:44 589最真的爱(转) 2011年11月25日 -
如何做好日用品的直销?
2012-01-17 06:44 656如何做好日用品的直销? 2011年11月22日 濡 -
安莉芳内衣亮相2011深圳国际内衣文化周
2012-01-17 06:44 648安莉芳内衣亮相2011深圳国际内衣文化周 2011年11月2 ... -
歌瑞尔内衣:一不小心“玩”大了
2012-01-17 06:44 690歌瑞尔内衣:一不小心“玩”大了 2011年11月26日 ... -
2011-12-12
2012-01-17 06:44 5462011-12-12 2011年12月12日 乱码体: -
给大学生学习ARM和FPGA的建议(转)
2012-01-16 05:33 1033给大学生学习ARM和FPGA的 ... -
HOOK API 函数跳转详解
2012-01-16 05:33 1032HOOK API 函数跳转详解 20 ... -
java的面试socket
2012-01-16 05:33 904java的面试socket 2010年05 ... -
Winsock 常用API函数
2012-01-16 05:26 651Winsock 常用API函数 2011年04月29日 ...
相关推荐
总的来说,"汇编教程 这是一个汇编实例"提供了一个学习和实践汇编语言的机会,通过对"案例7代码转换程序"的分析,我们可以深入探究汇编语言的精髓,增强对计算机硬件和程序执行的理解。无论是初学者还是经验丰富的...
汇编语言的每一个指令通常都对应一个特定的机器码,这些指令包括数据处理(如加法、减法)、内存访问、跳转控制(如条件分支、无条件跳转)以及输入/输出等。汇编程序实例通常包含以下几个部分: 1. 数据段(Data ...
这是一个简单的汇编语言实例,便于初学者理解汇编语言的结构框架,
本压缩包中的"51单片机汇编实例源程序"包含了多个实用的汇编语言程序示例,这些实例覆盖了51单片机开发过程中的常见应用场景,如基本输入输出、定时器/计数器操作、中断处理、串行通信等。通过学习和分析这些实例,...
总的来说,这个实例提供了一个学习AT89C52中断系统和汇编语言编程的好机会,尤其是对于那些希望深入了解微控制器操作和嵌入式系统开发的爱好者和初学者。通过实践,你可以更好地掌握如何利用中断来提升系统的响应...
《51单片机汇编实例大全》涵盖了51单片机编程的多个核心领域,旨在帮助初学者和进阶者深入理解51单片机的运作机制,并通过实际操作提高编程技能。51单片机是微控制器领域中最为经典和广泛应用的一种,其汇编语言作为...
STM32汇编实例LCD驱动是嵌入式系统开发中的一个重要环节,主要涉及到微控制器STM32、汇编语言编程以及LCD显示屏的硬件接口和驱动程序。STM32是一款基于ARM Cortex-M内核的微控制器,由意法半导体公司生产,广泛应用...
MASM是微软提供的一个宏汇编器,用于将汇编语言程序转换成机器码,而DEBUG则是一个调试工具,可以帮助程序员检查和调试汇编代码。 实验内容的第一部分是【6.3.1】,它涉及到字符数组的复制。在这个例子中,定义了两...
本实例演示如何使用汇编语言在一个数组中寻找大于42h的无符号数,并将其存放到特定的地址区域中。 4. 键盘输入字符串,输出大写字母 本实例演示如何使用汇编语言从键盘输入一个字符串,并将小写字母转换为大写字母...
在压缩包内的"汇编实例学习"文件中,应包含有关这两个实例的详细步骤、代码示例和可能的解释。通过仔细研究这些材料,你将能够逐步建立起对MIPS汇编编程的深刻理解和实践能力。同时,这些实例也可以作为进一步探索更...
ARM汇编实例程序4 ARM汇编实例程序4 ARM汇编实例程序4
c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 ..................
这个实例可能通过s3c44b0x的GPIO(通用输入/输出)接口,演示了如何设置和清除特定引脚,从而控制LED的状态。这有助于开发者熟悉硬件接口的使用和汇编语言的实践。 四、C与汇编混合编程 在实际开发中,C语言提供了...
《51单片机汇编语言设计实例教程》是一本专为学习51单片机汇编语言设计的实践教程,旨在帮助读者深入理解和掌握51单片机的内部结构及汇编语言编程技巧。本教程以丰富的实例贯穿始终,通过实际操作来提升读者的编程...
**实例背景**:该实例旨在展示如何利用PROTEUS和51单片机实现一个可调控的倒计时显示功能。具体而言,该程序允许用户通过外部按键调整倒计时的时间,并在倒计时期间控制LED指示灯的状态。 1. **电路设计**: - ...
每个实例都提供了一个学习点,从基础的输入输出操作到更复杂的系统集成。通过这些实例,开发者可以深入理解PIC微控制器的内部工作原理,掌握汇编语言编程,以及如何与各种外围设备交互。对于初学者来说,这些实例是...
"汇编语言入门例子"这个压缩包文件提供了一些基础的汇编语言编程实例,非常适合想要入门汇编的朋友们学习。 汇编语言的核心概念包括指令集、寄存器、地址和操作码。每个汇编程序都是由一系列指令构成的,这些指令...
汇编实例 冒泡法 从大到小排序 汇编实例 冒泡法 从大到小排序 汇编实例 冒泡法 从大到小排序 汇编实例 冒泡法 从大到小排序 汇编实例 冒泡法 从大到小排序 汇编实例 冒泡法 从大到小排序