开始
对于基于ARM的RISC处理器,GNU C编译器提供了在C代码中内嵌汇编的功能。这种特性提供了C代码没有的功能,比如手动优化软件关键部分的代码、使用相关的处理器指令。
__asm__ __volatile__("hlt"); "__asm__"表示后面的代码为内嵌汇编,"asm"是"__asm__"的别名。"__volatile__"表示编译器不要优化代码,后面的指令保留原样,"volatile"是它的别名。括号里面是汇编指令。
汇编指令模板
使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。
让我们以一个简单的例子开始:
asm(
"mov r0, r0nt"
"mov r0, r0"
); /* NOP 例子 */
该语句的作用是将r0移动到r0中。换句话讲他并不干任何事。典型的就是NOP指令,作用就是短时的延时。可以在一个asm声明中写多个汇编指令。但是为了增加程序的可读性,最好将每一个汇编指令单独放一行。换行符和制表符的使用可以使得指令列表看起来变得美观(不加n编译会报错)。
1.”NOP"指令即空指令,
2. 运行该指令时单片机什么都不做,但是会占用一个指令的时间。
3. 当指令间需要有延时(给外部设备足够的响应时间;或是软件的延时等),可以插入“NOP”指令。
通用的内嵌汇编模版是这样的:
asm(code : output operand list : input operand list : clobber list);
汇编和C语句之间的联系通过上面asm声明中可选的output operand list和input operand list。Clobber list后面再说。
下面是将C语言的一个整型变量传递给汇编,传递给C语言的另外一个整型变量。
int x = 5, y = 0;
__asm__("mov %[output], %[input]n" : [output] "=r"(y) : [input] "r" (x));
汇编指令
每一个asm语句被冒号(:)分成了四个部分。汇编指令放在第一部分中的“”中间。
"mov %[output], %[input]n"
操作数列表
接下来是冒号后的可选择的output operand list:input operand list,每一个条目是由一对[](方括号)和被他包括的符号名组成,它后面跟着限制性字符串,再后面是圆括号和它括着的C变量。 "output"前面的限制字符串是"=r",其中output"是输出操作数,"r" 表示需要将"result"与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是"output"本身,当然指令执行完后需要将寄存器中的值存入变量"output",从表面上看好像是指令直接对"result"进行操作,实际上GCC做了隐式处理,这样我们可以少写一 些指令。"input"前面的"r"表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的 汇编代码将会有错,读者可以将上例中的两个"r",都改为"m"(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
movl input, result
很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用"m"作为限定字符。
上面NOP例子的输出如下:
破坏符列表
有时候,当你想通知GCC当前内联汇编语句可能对某些寄存器和内存进行修改,希望GCC将这一点考虑进去,此时就可以在Clobber/Modify域中进行声明这些寄存器和内存。
这种情况一般发生在一个寄存器出现在Instructionlist,但不是有Output/Input操作表达式所指 定的,也不是在一些Output/Input操作表达式使用“r”约束时有GCC为其选择的,同时此寄存器被Instructionlist修改,而这个寄存器只是供当前内联汇编使用的情况。
例如:__asm__ (“mov R0, #0x34” ::: “R0”)
寄存器R0出现在Instructionlist中,且被mov指令修改,但却未被任何Output/Input操作表达式指定,所以需要在Clobber/Modify域中指定“R0”,让GCC知道这一点。
因为你在Output/Input操作表达式所指定的寄存器,或当你为一些Output/Input表达式使用“r”约束,GCC为你选择一个寄存器,编译器对这些寄存器是非常清楚的, 它知道这些寄存器是被修改的,因此不需要在Clobber/Modify域中在声明它们。除此之外,GCC对剩下的寄存器中哪些会被当前内联汇编修改一无所知。所以,如果当前内联汇编修改了这些寄存器,就最好在Clobber/Modify域中声明,让GCC针对这些寄存器做相应的处理,否则可能会造成寄存器 的不一致,造成程序执行错误。
注意事项
就像上面的NOP例子,asm声明的4个部分中,只要最尾部没有使用的部分都可以省略。但是有有一点要注意的是,上面的4个部分中只要后面的还要使用,前面的部分没有使用也不能省略,必须空但是保留冒号。为了增加代码的可读性,你可以使用换行,空格,还有C风格的注释。
在早期的C代码中还有如下写法:
asm("mov %0,%1" : "=r" (output) : "m" (input));
在汇编代码中操作数的引用使用的是%后面跟一个数字,%1代表第一个操作数,%2代码第二个操作数,称为占位符,往后的类推。操作数至多有10 个,用"%0","%1"...."%9"表示。这个方法目前最新的编译器还是支持的。但是它不便于维护代码。试想一下,你写了大量的汇编指令的代码,要是你想插入一个操作数,那么你就不得不从新修改操作数编号。
为什么需要汇编
- C限制了你更加贴近底层操作硬件,比如,C中没有直接修改程序状态寄存器(PSR)的声明。
- 写出更加优化的代码。
- 如果我们想做逆向工程,或者理解相关二进制程序的执行流程,构建ARM架构的shellcode,ROP链,以及调试ARM应用,这些都要求先懂得ARM汇编。
开始
对于基于ARM的RISC处理器,GNU C编译器提供了在C代码中内嵌汇编的功能。这种特性提供了C代码没有的功能,比如手动优化软件关键部分的代码、使用相关的处理器指令。
__asm__ __volatile__("hlt"); "__asm__"表示后面的代码为内嵌汇编,"asm"是"__asm__"的别名。"__volatile__"表示编译器不要优化代码,后面的指令保留原样,"volatile"是它的别名。括号里面是汇编指令。
汇编指令模板
使用内嵌汇编,要先编写汇编指令模板,然后将C语言表达式与指令的操作数相关联,并告诉GCC对这些操作有哪些限制条件。
让我们以一个简单的例子开始:
asm(
"mov r0, r0nt"
"mov r0, r0"
); /* NOP 例子 */
该语句的作用是将r0移动到r0中。换句话讲他并不干任何事。典型的就是NOP指令,作用就是短时的延时。可以在一个asm声明中写多个汇编指令。但是为了增加程序的可读性,最好将每一个汇编指令单独放一行。换行符和制表符的使用可以使得指令列表看起来变得美观(不加n编译会报错)。
1.”NOP"指令即空指令,
2. 运行该指令时单片机什么都不做,但是会占用一个指令的时间。
3. 当指令间需要有延时(给外部设备足够的响应时间;或是软件的延时等),可以插入“NOP”指令。
通用的内嵌汇编模版是这样的:
asm(code : output operand list : input operand list : clobber list);
汇编和C语句之间的联系通过上面asm声明中可选的output operand list和input operand list。Clobber list后面再说。
下面是将C语言的一个整型变量传递给汇编,传递给C语言的另外一个整型变量。
int x = 5, y = 0;
__asm__("mov %[output], %[input]n" : [output] "=r"(y) : [input] "r" (x));
汇编指令
每一个asm语句被冒号(:)分成了四个部分。汇编指令放在第一部分中的“”中间。
"mov %[output], %[input]n"
操作数列表
接下来是冒号后的可选择的output operand list:input operand list,每一个条目是由一对[](方括号)和被他包括的符号名组成,它后面跟着限制性字符串,再后面是圆括号和它括着的C变量。 "output"前面的限制字符串是"=r",其中output"是输出操作数,"r" 表示需要将"result"与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是"output"本身,当然指令执行完后需要将寄存器中的值存入变量"output",从表面上看好像是指令直接对"result"进行操作,实际上GCC做了隐式处理,这样我们可以少写一 些指令。"input"前面的"r"表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的 汇编代码将会有错,读者可以将上例中的两个"r",都改为"m"(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
movl input, result
很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用"m"作为限定字符。
上面NOP例子的输出如下:
破坏符列表
有时候,当你想通知GCC当前内联汇编语句可能对某些寄存器和内存进行修改,希望GCC将这一点考虑进去,此时就可以在Clobber/Modify域中进行声明这些寄存器和内存。
这种情况一般发生在一个寄存器出现在Instructionlist,但不是有Output/Input操作表达式所指 定的,也不是在一些Output/Input操作表达式使用“r”约束时有GCC为其选择的,同时此寄存器被Instructionlist修改,而这个寄存器只是供当前内联汇编使用的情况。
例如:__asm__ (“mov R0, #0x34” ::: “R0”)
寄存器R0出现在Instructionlist中,且被mov指令修改,但却未被任何Output/Input操作表达式指定,所以需要在Clobber/Modify域中指定“R0”,让GCC知道这一点。
因为你在Output/Input操作表达式所指定的寄存器,或当你为一些Output/Input表达式使用“r”约束,GCC为你选择一个寄存器,编译器对这些寄存器是非常清楚的, 它知道这些寄存器是被修改的,因此不需要在Clobber/Modify域中在声明它们。除此之外,GCC对剩下的寄存器中哪些会被当前内联汇编修改一无所知。所以,如果当前内联汇编修改了这些寄存器,就最好在Clobber/Modify域中声明,让GCC针对这些寄存器做相应的处理,否则可能会造成寄存器 的不一致,造成程序执行错误。
注意事项
就像上面的NOP例子,asm声明的4个部分中,只要最尾部没有使用的部分都可以省略。但是有有一点要注意的是,上面的4个部分中只要后面的还要使用,前面的部分没有使用也不能省略,必须空但是保留冒号。为了增加代码的可读性,你可以使用换行,空格,还有C风格的注释。
在早期的C代码中还有如下写法:
asm("mov %0,%1" : "=r" (output) : "m" (input));
在汇编代码中操作数的引用使用的是%后面跟一个数字,%1代表第一个操作数,%2代码第二个操作数,称为占位符,往后的类推。操作数至多有10 个,用"%0","%1"...."%9"表示。这个方法目前最新的编译器还是支持的。但是它不便于维护代码。试想一下,你写了大量的汇编指令的代码,要是你想插入一个操作数,那么你就不得不从新修改操作数编号。
为什么需要汇编
- C限制了你更加贴近底层操作硬件,比如,C中没有直接修改程序状态寄存器(PSR)的声明。
- 写出更加优化的代码。
- 如果我们想做逆向工程,或者理解相关二进制程序的执行流程,构建ARM架构的shellcode,ROP链,以及调试ARM应用,这些都要求先懂得ARM汇编。
举报