背景介绍
PowerPC于1991年IBM/MOTO/APPLE研制,大量应用于服务器(AIX / AS400系列及苹果系列服务器),家用游戏机(PS3, Wii, XBOX, GameCube),以及嵌入式(仅次于Arm/x86排第三)。PowerPC核心在于开放系统软件标准,其应用范围仅次于x86,是除去x86外最值得开发者了解的体系。
不需要写出非常高效的代码,但要了解基本效率原则;不需要大规模开发PPC程序,但需要时能写几段、调试时能看懂哪里错了。本文将从对比x86入手,引入RISC及PowerPC体系概念,向读者介绍该体系指令集,常用优化方法和交叉编译环境及模拟器的搭建等内容。
PowerPC基础知识
1990年 IBM时任总裁 Kuehler说服了摩托罗拉公司和苹果公司与IBM公司共同参与制订 PowerPC体系结构。为了让 AS/400也成为其中一员,1991 / 1992年罗彻斯特实验室开始为 AS/400扩充并制订PowerPC的64位结构。
----《罗彻斯特城堡》
大部分CPU指令集都可以分为:数据读写、数值计算、流程控制与设备管理四个部分,其中设备管理不属于介绍范围。开放系统软件标准在于硬件/软件只要符合该标准都能在 PowerPC下运行,也就是说先今有大量CPU虽然实现不一,但是他们在标准上都支持了 PowerPC体系,使得开发与接口更为方便。
PPC使用RISC(精简指令集),指令字长都是32bit,一条Intel指令往往可以由多条 PPC指令组合表示。Endian一般都是可调的,默认使用BE(Big Endian),同时PPC没有栈,也就是说应用程序需要自己实现相关操作。
常用术语介绍
常用寄存器
问题1:如何加载32位立即数?
在PPC下如何加载32位的立即数呢?RISC下PPC的每条指令都是4个字节定长。除去指令与寄存器参数编码,只有剩下16bit的长度用来描述立即数,比如立即数加载指令 LI:
LI rD, SIMM
立即数SIMM字段仅16位,如何表示32位?
答案:只有分两次载如,使用LIS(立即数载入并左移)和ADDI(立即数加法)分两次加载。因此32bit的立即数加载需要分两次完成:
LIS R3, 0x1122 加载并左移16位
ADDI R3, R3, 0x3344 再加上低16位
两条指令后,R3完成对 0x11223344的加载
特性:不一样的子程序调用
• f1: 子程序入口
• blr 返回(跳转到LR地址)
• start:
• bl f1 调用f1(跳转并保存地址到LR)
• li r1, 1 设置r1 = 1
• li r3, 1 设置r3 = 1
• sc 系统调用:结束程序
PPC使用了LR寄存器(Link Register)来完成:在bl指令跳转前,下条指令(li r1,1)的地址会被保存到LR而执行到f1中的blr时,系统会跳转到LR所表示的地址,完成返回。
数据读写指令
注意:LBZ R3, 0(R2)与LHZ R3,10(R2)并不全等同于MOV AL,[EBX]和MOV AX,[EBX+10]。前者将字节和半字加载到R3时顺便清空了高位,而后两条指令加载数据到EAX并不会清空高位。
第一个程序:Hello World !!
把下面的程序保存成 hello.s,并交叉编译:
# powerpc-eabi-as -gstabs hello.s -o hello.o
# powerpc-eabi-ld hello.o -o hello
• .global _start /* 请将本程序保存成 hello.s */
• .data /* 后面将讲解如何在虚拟机中调试 */
• msg: .asciz "Hello, PowerPC World !!/n"
• len = . - msg
• .text /* 代码部分开始 */
• _start:
• li %r0, 4 /* r0 = 4 */
• li %r3, 1 /* r3 = 1 */
• lis %r4, msg@ha /* r4 = msg(high) << 16 */
• addi %r4, %r4, msg@l /* r4 = r4 + msg(low) */
• li %r5, len /* r5 = len */
• sc /* system call (print) */
• li %r0, 1 /* r0 = 1 */
• li %r3, 1 /* r3 = 1 */
• sc /* system call (exit) */
完成交叉编译后用 qemu模拟器执行:
# qemu-ppc hello
Hello, PowerPC World !!
关于如何在x86环境下交叉编译与调试,详细见第三部分的的“PowerPC编译调试”。
特殊寄存器操作
问题2:没有栈仅靠LR如何递归?
• f1:
• mflr r2 保存LR中记录的地址到r2
• stw r2, -8(r1) 记录r2的数值到MEM[r1-8]处
• addi r1, r1, -60 r1后移60个字节,完成进栈操作
• ….
• addi r1, r1, 60 r1前移60个字节,准备出栈
• lwz r2, -8(r1) 读出老的LR值到r2
• mtfr r2 将r2的内容复制到LR
• blr 返回(跳转到LR地址)
• start:
• ….
虽然PPC没有直接提供栈相关指令(PUSH/POP/CALL/RET),应用程序却常用R1来模拟栈指针,实现多层调用时对LR的记录与恢复。 |