`
universsky
  • 浏览: 91909 次
文章分类
社区版块
存档分类
最新评论

AT&T 汇编 2

 
阅读更多


条件跳转:
条件跳转按照EFLAGS中的值来判断是否该跳转,格式为:

jxx address,其中xx是1-3个字符的条件代码,取值如下:
a 大于时跳转
ae大于等于
b小于
be小于等于
c进位
cxz如果CX寄存器为0
ecxz如果ECS寄存器为0
e相等
na 不大于
nae不大于或者等于
nb不小于
nbe不小于或等于
nc无进位
ne不等于
g大于(有符号)
ge大于等于(有符号)
l小于(有符号)
le小于等于(有符号)
ng不大于(有符号)
nge不大于等于(有符号)
nl不小于
nle不小于等于
no不溢出
np不奇偶校验
ns无符号
nz非零
o溢出
p奇偶校验
pe如果偶校验
po如果奇校验
s如果带符号
z如果为零

条件跳转不支持分段内存模型下的远跳转, 如果在该模式下进行程序设计必须使用程序逻辑确定条件是否存在,然后实现无条件跳转,跳转
前必须设置EFLAGS寄存器

比较:
cmp operend1, operend2


进位标志修改指令:
CLC清空进位标志(设置为0)
CMC对进位标志求反(把它改变为相反的值)
STC设置进位标志(设置为1)


循环:
loop循环直到ECX寄存器为0
loope/loopz循环直到ecx寄存器为0或者没有设置ZF标志
loopne/loopnz循环直到ecx为0或者设置了ZF标志

指令格式为: loopxx address注意循环指令只支持
8位偏移地址


###########################################################################
#####################
#六,数字
###########################################################################
#####################


IA-32平台中存储超过一字节的数都被存储为小尾数的形式但是把数字传递给寄存器时,寄存器里面保存是按照大尾数的形式存储


把无符号数转换成位数更大的值时, 必须确保所有的高位部分都被设置为零


把有符号数转换成位数更大的数时:
intel提供了movsx指令它允许扩展带符号数并保留符号,它与movzx相似,但是它假设要传送的字节是带符号数形式


浮点数:
fld指令用于把浮点数字传送入和传送出
FPU寄存器,格式:
fld source
其中source可以为32 64或者80位整数值


IA-32使用FLD指令用于把存储在内存中的单精度和双精度浮点值FPU寄存器堆栈中, 为了区分这两种长度GNU汇编器使用
FLDS加载单精度浮点数, FLDL加载双精度浮点数


类似
FST用于获取FPU寄存器堆栈中顶部的值,并且把这个值放到内存位置中,对于单精度使用FSTS,对于双精度使用FSTL


###########################################################################
#####################
#七,基本数学运算
###########################################################################
#####################
1,加法
ADD source, destination 把两个整数相加
其中source可以是立即数内存或者寄存器, destination可以是内存或者寄存器,但是两者不能同时都是内存位置


ADC和ADD相似进行加法运算,但是它把前一个ADD指令的产生进位标志的值包含在其中,在处理位数大于32(如64)
位的整数时,该指令非常有用

2,减法
SUB source, destination 把两个整数相减
NEG它生成值的补码
SBB指令,和加法操作一样,可以使用进位情况帮助执行大的无符号数值的减法运算. SBB在多字节减法操作中利用进位和溢出标志实现跨
数据边界的的借位特性

3,递增和递减
dec destination 递减
inc destination 递增

其中dec和inc指令都不会影响进位标志,所以递增或递减计数器的值都不会影响程序中涉及进位标志的其他任何运算

4,乘法
mul source进行无符号数相乘
它使用隐含的目标操作数,目标位置总是使用eax的某种形式,这取决与源操作数的长度, 因此根据源操作数的长度,目标操作数必须放在
AL, AX, EAX中。 此外由于乘法可能产生很大的值,目标位置必须是源操作数的两倍位置,源为8时,应该是16,源为16时,应该为32,但
是当源为16位时intel为了向下兼容,目标操作数不是存放在eax中,而是分别存放在DX:AX中,结果高位存储在DX中,地位存储在AX中。
对于32位的源,目标操作数存储在EDX:EAX中,其中EDX存储的是高32位, EAX存储的是低32位


imul source进行有符号数乘法运算,其中的目标操作数和mul的一样

imul source, destination也可以执行有符号乘法运算,但是此时可以把目标放在指定的位置,使用这种格式的缺陷
在与乘法的操作结果被限制为单一目标寄存器的长度.

imul multiplier, source, destination
其中multiplier是一个立即数,这种方式允许一个值与给定的源操作数进行快速的乘法运算,然后把结果存储在通用寄存器中

5,除法
div divisor执行无符号数除法运算
除数的最大值取决与被除数的长度,对于16位被除数 ,除数只能为8位, 32或64位同上
被除数 被除数长度 商余数
AX 16位 AL AH
DX:AX 32位 AX DX
EDX:EAX 64位 EAX EDX

idiv divisor执行有符号数的除法运算,方式和div一样

6,移位
左移位:
sal向左移位
sal destination 把destination向左移动
1位
sal %cl, destination把destination的值向左移动
CL寄存器中指定的位数
sal shifter, destination 把destination的值向左移动
shifter值指定的位数
向左移位可以对带符号数和无符号数执行向左移位的操作,移位造成的空位用零填充,移位造成的超过数据长度的任何位都被存放在进位标志
中,然后在下一次移位操作中被丢弃

右移位:
shr向右移位
sar向右移位
SHR指令清空移位造成的空位,所以它只能对无符号数进行移位操作
SAR指令根据整数的符号位,要么清空,要么设置移位造成的空位,对于负数,空位被设置为1

循环移位:
和移位指令类似,只不过溢出的位被存放回值的另一端,而不是丢弃
ROL 向左循环移位
ROR向右循环移位
RCL 向左循环移位,并且包含进位标志
RCR向右循环移位,并且包含进位标志


7,逻辑运算
AND OR XOR
这些指令使用相同的格式:
and source, destination
其中source可以是8位 16位或者32位的立即值 寄存器或内存中的值, destination可以是8位 16位或者32位寄存器或内存中的值,
不能同时使用内存值作为源和目标。 布尔逻辑功能对源和目标执行按位操作。
也就是说使用指定的逻辑功能按照顺序对数据的元素的每个位进行单独比较。

NOT指令使用单一操作数,它即是源值也是目标结果的位置
清空寄存器的最高效方式是使用OR指令对寄存器和它本身进行异或操作.当和本身进行XOR操作时,每个设置为1的位就变为0, 每个设
置为0的位也变位0。

位测试可以使用以上的逻辑运算指令,但这些指令会修改
destination的值,因此intel提供了test指令,它不会修改目标值而是设置相应的
标志


###########################################################################
#####################
#八,字符串处理

###########################################################################
#####################
1,传送字符串
movs有三种格式
movsb传送单一字节
movsw传送一个字
movsl传送双字

movs指令使用隐含的源和目的操作数, 隐含的源操作数是ESI, 隐含的目的操作数是EDI, 有两种方式加载内存地址到ESI和EDI,
第一种是使用标签间接寻址 movl $output, %ESI,第二种是使用lea指令, lea指令加载对象的地址到指定的目的操作数如lea output,
%esi,每次执行movs指令后, 数据传送后ESI和EDI寄存器会自动改变,为另一次传送做准备, ESI和EDI可能随着标志
DF的不同自动
递增或者自动递减, 如果DF标志为0则movs指令后ESI和EDI会递增,反之会递减, 为了设置DF标志,可以使用一下指令:
CLD将DF标志清零
STD设置DF标志


2,rep前缀
REP指令的特殊之处在与它不执行什么操作,这条指令用于按照特定次数重复执行字符串指令,有ECX寄存器控制,但不需要额外的loop指
令,如rep movsl


rep的其他格式:
repe等于时重复
repne不等于时重复
repnz不为零时重复
repz为零时重复


3,存储和加载字符串
LODS加载字符串, ESI为源,当一次执行完
lods时会递增或递减
ESI寄存器, 然后把字符串值存放到EAX中


STOS使用lods把字符串值加载到EAX后, 可以使用它把EAX中的值存储到内存中去:
stos使用EDI作为目的操作数,执行stos指令后,会根据
DF的值自动递增或者递减
EDI中的值


4,比较字符串
cmps和其他的操作字符串的指令一样,隐含的源和目标操作数都为ESI和EDI,每次执行时都会根据
DF的值把
ESI和EDI递增或者递减, cmps指令从目标字符串中减去源字符串,执行后会设置EFLAGS寄存器的状态.


5,扫描字符串
scas把EDI作为目标,它把EDI中的字符串和EAX中的字符串进行比较
,然后根据
DF的值递增或者递减
EDI


###########################################################################
#####################
#九,使用函数
###########################################################################
#####################
GNU汇编语言定义函数的语法:
.type标签(也就是函数名), @function
ret返回到调用处


###########################################################################
#####################
#十,linux系统调用
###########################################################################
#####################
linux系统调用的中断向量为0x80


1,系统调用标识存放在%eax中
2,系统调用输入值:
EBX第一个参数


ECX第二个参数
EDX第三个参数
ESI第四个参数
EDI第五个参数

需要输入超过
6个输入参数的系统调用, EBX指针用于保存指向输入参数内存位置的指针,输入参数按照连续的的顺序存储,系统调用的返回
值存放在EAX中

###########################################################################
#####################
#十一,汇编语言的高级功能
###########################################################################
#####################
1,gnu内联汇编的语法:
asm或__asm__("汇编代码");
指令必须包含在引号里
如果包含的指令超过一行 必须使用新行分隔符分隔


使用c全局变量,不能在内联汇编中使用局部变量,注意在汇编语言代码中值被用做内存位置,而不是立即数值

如果不希望优化内联汇编,则可以volatile修饰符如:__asm__ volatile("code");


2,GCC内联汇编的扩展语法
__asm__("assembly code":output locations:input operands:changed registers);


第一部分是汇编代码
第二部分是输出位置,包含内联汇编代码的输出值的寄存器和内存位置列表
第三部分是输入操作数,包含内联汇编代码输入值的寄存器和内存位置的列表
第四部分是改动的寄存器, 内联汇编改变的任何其他寄存器的列表
这几个部分可以不全有,但是没有的还必须使用:分隔


1,指定输入值和输出值, 输入值和输出值的列表格式为:
"constraint"(variable),其中variable是程序中声明的c变量,在扩展asm格式中, 局部和全局变量都可以使用,使用constrant(约束)
定义把变量存放到哪(输入)或从哪里传送变量(输出)
约束使用单一的字符,如下:
约束描述
a 使用%eax, %ax, %al寄存器
b使用%ebx, %bx, %bl寄存器
c使用%ecx, %cx, %cl寄存器
d使用%edx, %dx, %dl寄存器
S使用%esi, %si寄存器
D使用%edi, %di寄存器
r使用任何可用的通用寄存器
q使用%eax, %ebx, %ecx,%edx之一
A对于64位值使用%eax, %edx寄存器
f使用浮点寄存器
t使用第一个(顶部)的浮点寄存器
u 使用第二个浮点寄存器
m使用变量的内存位置
o使用偏移内存位置
V只使用直接内存位置
i使用立即整数值
n 使用值已知的立即整数值
g使用任何可用的寄存器和内存位置

除了这些约束之外,输出值还包含一个约束修饰符:
输出修饰符 描述
+可以读取和写入操作数
=只能写入操作数
%如果有必要操作数可以和下一个操作数切换
&在内联函数完成之前,可以删除和重新使用操作数


如:
__asm__("assembly code": "=a"(result):"d"(data1),"c"(data2));
把c变量data1存放在edx寄存器中,把c变量data2存放到ecx寄存器中,内联汇编的结果将存放在eax寄存器中,然后传送给变量
result


在扩展的asm语句块中如果要使用寄存器必须使用两个百分号符号


不一定总要在内联汇编代码中指定输出值,一些汇编指令假定输入值包含输出值,如movs指令


其他扩展内联汇编知识:
1,使用占位符
输入值存放在内联汇编段中声明的特定寄存器中,并且在汇编指令中专门使用这些寄存器.虽然这种方式能够很好的处理只有几个输入值的情
况,但对于需要很多输入值的情况,这中方式显的有点繁琐.为了帮助解决这个问题,扩展asm格式提供了占位符,可以在内联汇编代码中使
用它引用输入和输出值.


占位符是前面加上百分号的数字,按照内联汇编中列出的每个输入和输出值在列表中的位置,每个值被赋予从0开始的地方.然后就可以在汇
编代码中引用占位符来表示值。


如果内联汇编代码中的输入和输出值共享程序中相同的c变量,则可以指定使用占位符作为约束值,如:
__asm__("imull %1, %0"
: "=r"(data2)
: "r"(data1), "0"(data2));
如输入输出值中共享相同的变量data2, 而在输入变量中则可以使用标记
0作为输入参数的约束


2,替换占位符
如果处理很多输入和输出值,数字型的占位符很快就会变的很混乱,为了使条理清晰
,GNU汇编器(从版本3.1开始)允许声明替换的名称作为
占位符.替换的名称在声明输入值和输出值的段中定义,格式如下:
%[name]"constraint"(variable)
定义的值name成为内联汇编代码中变量的新的占位符号标识,如下面的例子:
__asm__("imull %[value1], %[value2]"
: [value2] "=r"(data2)
: [value1] "r"(data1), "0"(data2));


3,改动寄存器列表
编译器假设输入值和输出值使用的寄存器会被改动,并且相应的作出处理。程序员不需要在改动的寄存器列表中包含这些值,如果这样做了,就
会产生错误消息.注意改动的寄存器列表中的寄存器使用完整的寄存器名称,而不像输入和输出寄存器定义的那样仅仅是单一字母。 在寄存器
名称前面使用百分号符号是可选的。


改动寄存器列表的正确使用方法是,如果内联汇编代码使用了没有被初始化地声明为输入或者输出值的其他任何寄存器 ,则要通知编译器。编
译器必须知道这些寄存器,以避免使用他们。如:
int main(void) {
int data1 = 10;
int result = 20;


__asm__("movl %1, %%eax\n\t"
"addl %%eax, %0"
: "=r"(result)
: "r"(data1), "0"(result)
: "%eax");
printf("The result is %d\n", result);
return 0;
}


4,使用内存位置
虽然在内联汇编代码中使用寄存器比较快,但是也可以直接使用c变量的内存位置。约束
m用于引用输入值和输出值中的内存位置。记住,对
于要求使用寄存器的汇编指令,仍然必须使用寄存器,所以不得不定义保存数据的中间寄存器。如:
int main(void) {

int dividentd = 20;
int divisor = 5;
int result;


__asm__("divb %2\n\t"
"movl %%eax, %0"
: "=m"(result)
: "a"(dividend), "m"(divisor));
printf("The result is %d\n", result);
return 0;
}


5,处理跳转
内联汇编语言代码也可以包含定义其中位置的标签。 可以实现一般的汇编条件分支和无条件分支, 如:


int main(void) {
int a = 10;
int b = 20;
int result;


__asm__("cmp %1, %2\n\t"
"jge greater\n\t"
"movl %1, %0\n\t"
"jmp end\n"
"greater:\n\t"
"movl %2, %0\n"
"end:"
:"=r"(result)
:"r"(a), "r"(b));
printf("The larger value is %d\n", result);
return 0;
}


在内联汇编代码中使用标签时有两个限制。第一个限制是只能跳转到相同的asm段内的标签,不能从-个asm段跳转到另一个asm段中的
标签。第二个限制更加复杂一点。 以上程序使用标签
greater和end。 但是 ,这样有个潜在的问题,查看汇编后的代码清单, 可以发现内联
汇编标签也被编码到了最终汇编后的代码中。 这意味着如果在c代码中还有另一个asm段,就不能再次使用相同的标签,否则会因为标签重
复使用而导致错误消息。还有如果试图整合使用c关键字(比如函数名称或者全局变量)的标签也会导致错误。

###########################################################################
#####################
#十二,优化你的代码
###########################################################################
#####################
GNU编译器提供-O选项供程序优化使用:
-O提供基础级别的优化
-O2提供更加高级的代码优化
-O3提供最高级的代码优化
不同的优化级别使用的优化技术也可以单独的应用于代码。 可以使用-f命令行选项引用每个单独的优化技术。


1, 编译器优化级别
1
在优化的第一个级别执行基础代码的优化。 这个级别试图执行9种单独的优化功能:
-fdefer-pop:这种优化技术与汇编语言代码在函数完成时如何进行操作有关。 一般情况下,函数的输入值被保存在堆栈种并且被函数访问。
函数返回时,输入值还在堆栈种。 一般情况下,函数返回之后,输入值被立即弹出堆栈。这样做会使堆栈种的内容有些杂乱。

-fmerge-constans:使用这种优化技术,编译器试图合并相同的常量.这一特性有时候会导致很长的编译时间,因为编译器必须分析
c或者
c++程序中用到的每个常量,并且相互比较他们.

-fthread-jumps:使用这种优化技术与编译器如果处理汇编代码中的条件和非条件分支有关。 在某些情况下,一条跳转指令可能转移到另一
条分支语句。 通过一连串跳转,编译器确定多个跳转之间的最终目标并且把第一个跳转重新定向到最终目标。

-floop-optimize:通过优化如何生成汇编语言中的循环, 编译器可以在很大程序上提高应用程序的性能。 通常 ,程序由很多大型且复杂的循

环构成。 通过删除在循环内没有改变值的变量赋值操作,可以减少循环内执行指令的数量,在很大程度上提高性能。 此外优化那些确定何时离
开循环的条件分支, 以便减少分支的影响。

-fif-conversion: if-then语句应该是应用程序中仅次于循环的最消耗时间的部分。简单的if-then语句可能在最终的汇编语言代码中产生众多
的条件分支。 通过减少或者删除条件分支,以及使用条件传送 设置标志和使用运算技巧来替换他们,编译器可以减少
if-then语句中花费的时
间量。

-fif-conversion2:这种技术结合更加高级的数学特性, 减少实现
if-then语句所需的条件分支。

-fdelayed-branch:这种技术试图根据指令周期时间重新安排指令。 它还试图把尽可能多的指令移动到条件分支前,以便最充分的利用处理
器的治理缓存。

-fguess-branch-probability:就像其名称所暗示的,这种技术试图确定条件分支最可能的结果,并且相应的移动指令,这和延迟分支技术类
似。因为在编译时预测代码的安排,所以使用这一选项两次编译相同的c或者c++代码很可能会产生不同的汇编语言代码, 这取决于编译时
编译器认为会使用那些分支。因为这个原因,很多程序员不喜欢采用这个特性,并且专门地使用-fno-guess-branch-probability选项关闭这
个特性

-fcprop-registers:因为在函数中把寄存器分配给变量,所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并
且删除不必要的寄存器复制操作。

2,编译器优化级别
2
结合了第一个级别的所有优化技术,再加上一下一些优化:
-fforce-mem:这种优化再任何指令使用变量前,强制把存放再内存位置中的所有变量都复制到寄存器中。 对于只涉及单一指令的变量,这样
也许不会有很大的优化效果.但是对于再很多指令(必须数学操作)中都涉及到的变量来说,这会时很显著的优化,因为和访问内存中的值相比 ,
处理器访问寄存器中的值要快的多。

-foptimize-sibling-calls:这种技术处理相关的和/或者递归的函数调用。 通常,递归的函数调用可以被展开为一系列一般的指令, 而不是
使用分支。 这样处理器的指令缓存能够加载展开的指令并且处理他们,和指令保持为需要分支操作的单独函数调用相比,这样更快。

-fstrength-reduce:这种优化技术对循环执行优化并且删除迭代变量。 迭代变量是捆绑到循环计数器的变量,比如使用变量,然后使用循环
计数器变量执行数学操作的for-next循环。

-fgcse: 这种技术对生成的所有汇编语言代码执行全局通用表达式消除历程。 这些优化操作试图分析生成的汇编语言代码并且结合通用片段,
消除冗余的代码段。如果代码使用计算性的goto, gcc指令推荐使用-fno-gcse选项。

-fcse-follow-jumps:这种特别的通用子表达式消除技术扫描跳转指令,查找程序中通过任何其他途径都不会到达的目标代码。这种情况最常
见的例子就式if-then-else语句的else部分。

-frerun-cse-after-loop:这种技术在对任何循环已经进行过优化之后重新运行通用子表达式消除例程。这样确保在展开循环代码之后更进一
步地优化还编代码。

-fdelete-null-pointer-checks:这种优化技术扫描生成的汇编语言代码,查找检查空指针的代码。 编译器假设间接引用空指针将停止程序。
如果在间接引用之后检查指针, 它就不可能为空。

-fextensive-optimizations:这种技术执行从编译时的角度来说代价高昂的各种优化技术,但是它可能对运行时的性能产生负面影响。

-fregmove:编译器试图重新分配
mov指令中使用的寄存器,并且将其作为其他指令操作数,以便最大化捆绑的寄存器的数量。

-fschedule-insns:编译器将试图重新安排指令,以便消除等待数据的处理器。 对于在进行浮点运算时有延迟的处理器来说, 这使处理器在
等待浮点结果时可以加载其他指令。

-fsched-interblock:这种技术使编译器能够跨越指令块调度指令。 这可以非常灵活地移动指令以便等待期间完成的工作最大化。

-fcaller-saves:这个选项指示编译器对函数调用保存和恢复寄存器,使函数能够访问寄存器值,而且不必保存和恢复他们。 如果调用多个函
数,这样能够节省时间,因为只进行一次寄存器的保存和恢复操作,而不是在每个函数调用中都进行。

-fpeephole2:这个选项允许进行任何计算机特定的观察孔优化。

-freorder-blocks:这种优化技术允许重新安排指令块以便改进分支操作和代码局部性。

-fstrict-aliasing:这种技术强制实行高级语言的严格变量规则。 对于c和c++程序来说,它确保不在数据类型之间共享变量.例如,整数变
量不和单精度浮点变量使用相同的内存位置。


-funit-at-a-time:这种优化技术指示编译器在运行优化例程之前读取整个汇编语言代码。 这使编译器可以重新安排不消耗大量时间的代码以
便优化指令缓存。 但是,这会在编译时花费相当多的内存,对于小型计算机可能是一个问题。

-falign-functions:这个选项用于使函数对准内存中特定边界的开始位置。 大多数处理器按照页面读取内存,并且确保全部函数代码位于单
一内存页面内,就不需要叫化代码所需的页面。

-fcrossjumping:这是对跨越跳转的转换代码处理, 以便组合分散在程序各处的相同代码。 这样可以减少代码的长度, 但是也许不会对程
序性能有直接影响。

3,编译器优化级别
3
它整合了第一和第二级别中的左右优化技巧,还包括一下优化:
-finline-functions:这种优化技术不为函数创建单独的汇编语言代码, 而是把函数代码包含在调度程序的代码中。 对于多次被调用的函数
来说,为每次函数调用复制函数代码。 虽然这样对于减少代码长度不利,但是通过最充分的利用指令缓存代码,而不是在每次函数调用时进行
分支操作,可以提高性能。

-fweb:构建用于保存变量的伪寄存器网络。伪寄存器包含数据,就像他们是寄存器一样,但是可以使用各种其他优化技术进行优化,比如cse
和loop优化技术。

-fgcse-after-reload:这中技术在完全重新加载生成的且优化后的汇编语言代码之后执行第二次gcse优化,帮助消除不同优化方式创建的
任何冗余段。

二、Hello World!

真不知道打破这个传统会带来什么样的后果,但既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串
"Hello World!",那我们也以这种方式来开始介绍
Linux下的汇编语言程序设计。


在 Linux操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用 Linux内核提供的
系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如 libc这样的函数库,也不
需要使用 ELF解释器,因而代码尺寸小且执行速度快。
Linux是一个运行在保护模式下的 32位操作系统,采用 flat memory模式,目前最常用到的是 ELF格式的二进制代码。
一个 ELF格式的可执行程序通常划分为如下几个部分:.text、.data和 .bss,其中 .text是只读的代码区,.data是可读
可写的数据区,而 .bss则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要
你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF可执行程序至少应该有一个 .text部分。 下
面给出我们的第一个汇编程序,用的是 AT&T汇编语言格式:
例1. AT&T格式

#hello.s

.data # 数据段声明
msg : .string "Hello, world!\n" # 要输出的字符串
len = . -msg # 字串长度
.text # 代码段声明


.global _start # 指定入口函数

_start: # 在屏幕上显示一个字符串
movl $len, %edx # 参数三:字符串长度
movl $msg, %ecx # 参数二:要显示的字符串
movl $1, %ebx # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
int $0x80 # 调用内核功能

# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能

初次接触到 AT&T格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在 Linux平台上你同样可以使用
Intel格式来编写汇编程序:
例2. Intel格式

; hello.asm
section .data ;数据段声明
msg db "Hello, world!", 0xA ;要输出的字符串
lenequ$ -msg ;字串长度

section .text ;代码段声明
global _start ;指定入口函数
_start: ;在屏幕上显示一个字符串

mov edx, len ;参数三:字符串长度
mov ecx, msg ;参数二:要显示的字符串
movebx,1 ;参数一:文件描述符(stdout)
moveax,4 ;系统调用号(sys_write)
int 0x80 ;调用内核功能


;退出程序
movebx,0 ;参数一:退出代码
moveax,1 ;系统调用号(sys_exit)
int 0x80 ;调用内核功能

上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用 Linux内核提供的 sys_write来显示一个字符串,然后
再调用 sys_exit退出程序。在 Linux内核源文件
include/asm-i386/unistd.h中,可以找到所有系统调用的定义。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics