posts - 225, comments - 62, trackbacks - 0, articles - 0
   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

在Xbox360和PS3上的汇编嵌入

Posted on 2010-11-22 21:59 魔のkyo 阅读(1019) 评论(3)  编辑 收藏 引用

在PS3上使用C/C++代码嵌入的汇编和用GCC生成出来的汇编并不相同。

Xbox360和PS3都是使用的PowerPC的指令集,不过因为编译器不同,硬件体系结构不同,使用上还是有一些差异

在XBox360 SDK的Document中通过索引PowerPC Calling Convention Overview可以取得一些有关汇编信息,
在ppc_pem.pdf 8.2PowerPC Instruction Set(Page376~)里面有每条指令详细的说明。

PC和Xbox360上使用编译器cl.exe以下简称VC,PS3上使用的编译器PS3专用的GCC以下简称GCC

在VC,GCC上嵌入汇编都是使用__asm关键字
在VC上的用法是
以 __asm{ 标记开始嵌入汇编
   期间的汇编语句以换行分割,每行一句,可以使用分号结尾但不必须.
以 } 标记结束嵌入汇编

在GCC上的用法是
以 __asm( 标记开始嵌入汇编
   期间的所有汇编语句放在字符串内,并以分号分割
以 ); 标记结束嵌入汇编

因为编译器会把两个只由空白字符分割的字符串看作一个字符串,因此
__asm("stw r12,-8(SP); addi SP,SP,-8;");

__asm(
    "stw  r12,-8(SP);\
     addi SP,SP,-8;"
    );
以及
__asm(
    "stw  r12,-8(SP);"
    "addi SP,SP,-8;"
    );
是等价的,写成最下面的形式更清晰一些。


取得全局变量int g_Val;的地址放入r3
Xbox360
    __asm{
        lau     r3, g_Val
        lal     r3, r3, g_Val
    }
   
PS3
    __asm(
        "lis r3, g_Val@ha;"
        "addi r3, r3, g_Val@l;"
        );
360上没有@ha,@l这样的用法,而PS3上不支持lau,lal指令,360和PS3两者都不支持la指令。


取得全局变量int g_Val;的值放入r3
Xbox360
    __asm{
        lau     r3, g_Val
        lwz     r3, g_Val(r3)
    }

PS3
    __asm(
        "lis r3, g_Val@ha;"
        "addi r3, r3, g_Val@l;"
        "lwz r3, 0(r3);"
        );
或者
    __asm(
        "lis r3, g_Val@ha;"
        "lwz r3, g_Val@l(r3);"
        );

·关于push和pop

//---push r12---
stw     r12,-8(SP)
addi    SP,SP,-8
//--------------

//---pop r12----
addi    SP,SP,8
lwz  r12, -8(SP)
//--------------

//---对应x86的pushad---
stmw    r2,-120(SP)
addi    SP,SP,-120
//---------------------


//---对应x86的popad---
addi    SP,SP,120
lmw     r2,-120(SP)
//--------------------


·关于PowerPC中的跳转指令
比较常用的
jmp等价于b
jz的实现方式如下:
        cmpwi       cr6,r14,0     // if(r14 == 0)
        beq         cr6,Return    //     goto Return

PowerPC的跳转指令非常多,详见ppc_pem.pdf Page721~730

·关于函数调用,函数参数传递 和 函数返回值
用XBox360编译器编译出的C/C++函数总是会将参数依次从 20(SP)往下(往高字节,堆栈是向低字节增长的)压入

堆栈,其中SP是进入函数前的SP。

在PS3上情况也是类似的,从函数头的汇编可以看到依次使用了 -0x90(SP),-0x08(SP),0x10(SP),0x30

(SP),0x38(SP)
在PS3文档 OS Low-level Specifications 一章中可以看到这样一段话
The 288 bytes below the stack pointer must be available as volatile storage that is not preserved

across function calls.
Interrupt handlers and any other functions that might run without an explicit call must preserve

this region.
在Xbox360文档 PowerPC Calling Convention Overview 一章中可以看到这样一段话
Linkage Area
The linkage area is present if the current function calls other functions. In leaf functions, the

linkage area may or may not be present, depending upon whether the red zone can be used. The red

zone refers to the 1344 bytes of slack space past the end of the stack. The compiler can use this

space without first adjusting the stack pointer (R1). Leaf functions that use less than 1344

bytes of stack space can avoid allocating a stack frame.
If the red zone cannot be used, whenever a leaf function uses stack space the linkage area is

necessary. If the red zone can be used and the leaf function requires more space than the red

zone has to offer, the linkage area is necessary. The linkage area contains a back chain to the

caller's stack frame and is aligned on a double word boundary.

void fun2(void* param1, void* param2)
fun2(void*, void*)
000108FC F821FF71 stdu       r1,-0x90(r1)                   PIPE
00010900 7C0802A6 mfspr      r0,lr                         02
00010904 FBE10088 std        r31,0x88(r1)                   PIPE
00010908 F80100A0 std        r0,0xA0(r1)                  
0001090C 7C3F0B78 mr         r31,r1                         PIPE
00010910 7C601B78 mr         r0,r3                        
00010914 7C892378 mr         r9,r4                          PIPE
00010918 901F00C0 stw        r0,0xC0(r31)                 
0001091C 913F00C8 stw        r9,0xC8(r31)                   PIPE

所以在汇编中如果有调用C/C++函数的情况,比较安全的做法可以先将SP上移1344再跳转过去

XBox360是通过寄存器传递参数,R3–R10传递泛整型(包括int/long/short/char/所有整型和所有类型指针)参

数,FPR1–FPR13传递浮点型参数,在函数中也是通过寄存器取得传递过来的参数而不能通过变量名,函数返回

值也是通过R3/FPR1传递


·关于PowerPC的函数调用和返回机制
在x86指令集上通过call语句进行函数调用,调用时会将call语句下一条指令地址压入堆栈,在执行到ret时,

弹出堆栈赋值给IP,以此完成返回。
在PowerPC指令集上一般使用bl指令调用函数,其意义是将当前IP(即bl的下一条指令地址)赋值给一个称为LR

(link register)的寄存器,再将bl后面的值赋值给IP,以此实现函数调用,函数返回时一般通过跳转指令blr

返回,而blr指令就是将LR中的地址赋值给IP。
如果在PowerPC上定义naked函数,注意要手动保护LR
例如:bl function1时,会将此语句的下一条指令地址赋值给LR,而在function1中遇到blr指令时根据LR返回

。如果function1中bl function2,那么LR将被覆盖。安全的做法是:
__asm{
 //保存LR
 mflr             r0                //将LR赋给r0
 stw              r0,-8(SP)         //压入r0
 stwu             SP,-60h(SP)

 ....

 //恢复LR
 addi             SP,SP,60h         //弹出r0
 lwz              r0,-8(SP)
 mtlr             r0                //将r0赋给LR

 //返回
 blr                                //跳转到LR
}

在PS3上要给GCC加上如下编译参数
-Xassembler -mregnames
在360上浮点寄存器名是fp0~fp31,在PS3上是f0~f31,从内存读数据到浮点寄存器的指令在360上是lfd,stfd,

在PS3上是lfs,stfs

·如何在GCC上实现VC的__declspec(naked)
在VC中我们通过在函数声明前加上__declspec(naked)阻止编译器生成函数的固定头尾,一般编译器会在头尾处生成以下代码
push        ebp 
mov         ebp,esp
//...函数体的汇编
mov         esp,ebp
pop         ebp 
ret
有时我们希望把整个函数做成汇编的,并且阻止编译器生成这些头尾。(虽然这种需求是极少的)
我们便可以这样做,
__declspec(naked) int func()
{
    __asm{
       //...
    }
}

在GCC上实现这个有些困难,有些GCC上可以使用等价的__attribute__((naked)),但有些版本的GCC不支持,这时我们不妨把需要naked的函数放到一个独立的文件,然后使用GCC的 -S参数对这个文件进行编译,会生成一个.s的汇编文件,然后会这个.s文件进行编辑,去掉其中的函数头尾,再从项目中去掉原来的.c或.cpp文件,将修改后的.s文件加入

Feedback

# YMMD with that anwser! TX  回复  更多评论   

2011-05-23 04:13 by Mattingly
YMMD with that anwser! TX
只有注册用户登录后才能发表评论。