[自译挖坑]《PSP模块教程》(ModuleTutorialv1)
本帖最后由 natuki 于 2009-10-29 22:45 编辑
[自译挖坑]《PSP模块教程》(ModuleTutorialv1)
译到8.1.5没动力了,考试……挖坑提醒自己,也希望有心人拍砖指导……
=====
模块,输出,输入,补丁
一,建立模块,纯输出/程序。
二,后台到底发生了啥,内核如何管理模块及其IO。
三,如何动态patch内存里的模块,以改变系统行为满足需要。
读者要懂点C ,PSPSDK(怎么编译跑一个HELLOworld),要是懂点MIPS汇编的基础概念会更好。
1 引言
最为PSP开发新手,你大概装了PSPSDK,玩了玩cygwin,或者装了个本地POSIX环境……然后你试着编译运行了SDK的示例。你多少熟悉C语言编程,你知道Makefile咋工作,还有点儿汇编的基本知识(MIPS汇编,我们只是碰到点儿简单的,像寄存器/CPU的指令集),然后往下看。
读了些东西google了些东西,你知道PRX和ELF的不同,知道内核模式和用户模式是啥,你写了第一个helloworld程序,创建了自己的EBOOT.PBP。
现在你问“嗯……接下来做啥?”,你面前有两条路:你可以加入内核一方,关心内核内部的细节;或者加入用户一方,考虑PSP和PSPSDK作为一组API,在上面做自制程序。不管是那边,我们都希望这里解释的东西会有点儿用。
2 目标和非目标
这个教程打算说说“下一步做啥”对于PSP编程来说,试图填上“我能跑一个helloworld”和“我能写自己的固件和降级程序”之间的空白。(提醒一下这空白还很大)不是从零开始的教程,下面几点不会讲,许多教程已经讲过了:
。怎么用SDK建开发环境。虽然不是直接必要,但是你需要知道如何编译自己的例子和代码片段。
。清楚“helloworld”main.c文件和相关的makefile结构。
。知道怎么配置PSP跑自制程序,不管是用啥固件
。知道怎么用PSPLINK启动,传输,及调试程序
。盗版,改芯片,非法动作
在这个教程里面,要考虑下边的话题:
。非常基础的MIPS汇编。最好懂点MIPS汇编指令和CPU寄存器,会帮助你理解细节
。如何建立模块(像PRX),定义输出库与输出函数(纯输出模块)。由于技术和设计原因(目前的PSPSDK不支持)只能从已知模块输出函数,而不能是变量。注意这不是致命问题,你想输出变量的时候,可以总是包装起来或者改变属性。像int get_i()和void set_i()来对付i的值。
。如何建立其他的模块用于输入输出函数。管道的角色,如何建立输入表,什么是NID,怎么计算。
。如何动态建立可执行输出函数。
。PSP内核如何管理加载内核。抽象数据类型包含什么,模块性息如何保存于内存。
。如何动态修改已存在的模块(“patching”补丁),其实用的具体情况。
根据这教程更多目的,需要一个1.5的固件。我个人用的是PSPSDK的最近子版本,在Linux系统上,我的固件是3.03OE-C。这教程里我们在内核模式工作是为了简便,也是为了些技术原因。技术原因是,我们要看看模块系统的内部细节,而有些函数像sceKernelFindModuleByName只能在内核模式。为了简便是因为,要想克服这些限制,就需要做点小手脚,像是包装函数,或者让不受限用户访问内核呼叫的方法,即名叫“桥”,例如kubridge或者kvshbridge,这就会超出教程范围了。结果的EBOT.PBP应当放到GAME150文件夹里,并且包含一个静态ELF在主程序中。EBOOT.PBP和需要的PRX们放在一起。
3 感谢
我没装做这里的想法都是我的或者原创的。有些技术和计算机科学一样老,不限于PSP。PSP相关的信息收集于一些源,站点,典型的有:
。PSPDEV 站点http://www.pspdev.org 以及它论坛许多帖子
。MPH站点,破解NID函数注目
。Tyranid 优秀的PSPLink,PSPSDK和PRXTOOL程序
。PspPet 如PSARDumper之类的主意和代码
。Moonlight / Dark_Alex 自制固件理念的证明
。google朋友
。还有很多,很多……
4 系统模型
整个教程,我们考虑一下系统模型:
一个叫“客户端”的实体,从一个叫“服务器”的实体请求服务,“服务器”依次参考另外一个“管理员”实体,“管理员”实体基于一个标识符对客户端进行提权。“管理员”是一个封闭的系统,并且可以抽出低层操作。
每个系统都以一个模块来实现。服务器和管理员应该是输出模块。
5 PRX 与 ELF 模块
5.1 啥是ELF?
ELF (Executable and Linking Format) 是目标文件即 object file 的一种格式。ELF对于可执行来说,是可重定位以及共享对象(一般是模块),正如GIF或JPG之于图像。ELF标准从基础上确定一个文件的二进制格式。在简单情况下,它定义一个ELF头部,后跟若干段。每个段有一个目的,例如包含模块程序指令,定义模块输出函数,定义模块输入函数;包括已初始化。
5.2 啥是PRX?
PRX (Relocatable eXecutable)类似ELF。(这段待补完)
5.3 啥是EBOOT?
EBOOT.PBP文件类似于Windows里的exe文件。它将主可执行文件和其他元数据(metadata)文件打包在一起,如用于在XMB下显示的.png文件,同样用于在XMB下显示的.sfo视频文件。PBP文件的格式是简单的:它有一个固定大小的PBP头部,包括:一个4byte的幻数(magic number译)('\0','P','B','P'),一个u32版本号,8个偏移到打包的文件——“PARAM.SFO”,“ICON1.PMF”,“UNKNOWN.PNG”,“PIC1.PNG”,“SND0.AT3”,“UNKNOWN.PSP”和“UNKNOWN.PSAR”。
5.4 限制
使用OE固件时,1.5内核可以启动一个EBOOT中的静态elf,而不用对其进行kxploit,不论是在内核模式还是用户模式。对于3.03固件,模块则需要是PRX格式并且在用户模式下。从3.10 OE开始,模块如果在用户模式下,则可以是静态ELF和PRX。因为OE固件是破解过的,所以你可以从放在MS上的用户模式,使用内核模式加载并启动模块,如果你需要内核模式的程序,创建一个用户模式的外壳,用来加载启动内核内存区的主程序。
6 初始化
6.1 模块入口:crt0 ,启动文件,以及module_start
当PSP内核加载,启动一个模块,例如一个程序时,它读取EBOOT.PBP文件,抽取出ELF或者PRX模块(二者在此教程的目的下基本上是一个东西,仅有几点细节区别,如固定与可重定位的内存地址),分析模块段,进行一些链接,来用已加载的模块输出 和系统呼叫“连接”模块的输入,在内存中创建一份复制,然后进入到模块入口。
另外模块的开发者需要做决定,是在一个新的独立的线程来执行模块入口,还是让申请加载与启动模块的线程来执行。前者意味着一个叫“主线程”的建立(main这名字和经典C函数的main没关系),后者意味着你需要仔细参考在入口点会进行什么样的操作:若模块将要被用作函数库,或只是简单加载另外的工作线程,入口点就会是简单的,并且不需要独立的线程,但是如果要做更复杂的事,就需要个主线程(没有主线程就不能在入口点中休眠)。有定义主线程特点的宏,如是否在内核模式运行,vsh模式,或用户模式,是否会使用VFPU,等等。
因此,内核需要两个基本的东西,a)要能够读取/分析模块信息,b)知道模块入口点在哪儿能找到。特别地,内核希望能找到ELF/PRX中的段,段给出了模块信息,模块输出标识符(函数),叫做module_start。
不断的重复同样的任务,敲入需要的boilerplate代码是很不舒服的工作。因此,SDK抽象出一些细节,提供给你舒适的宏,作为替代。
6.1.1 模块信息
使用宏 PSP_MODULE_INFO 可以容易的给出需要的模块性息。PSP_MODULE_INFO("Module name", 0x1000, 1, 0);
复制代码这个宏用于陈述模块的名字,模块的“模式”(内核,用户或VSH),以及主副版本(v1.0)。在内部,这个宏声明sceModuleInfo来包含模块属性,模块名字,以及指针,指针指向包含模块输出(显出以供他用)输入(从其他模块使用)标识符的表格,并且定义需要的ELF/PRX段,内核将会在段里寻找这些信息。/*模块信息结构, 用于声明一个模块 (库 或 可执行)。此结构在所有的PSP可执行里面都需要。*/
typedef struct _scemoduleinfo {
unsigned short modattribute;
unsigned char modversion[2];
char modname[27];
char terminal;
void * gp_value;
void * ent_top;
void * ent_end;
void * stub_top;
void * stub_end;
} _sceModuleInfo;
typedef const _sceModuleInfo SceModuleInfo;
复制代码关于宏的细节,查看pspsdk/src/user/pspmoduleinfo.h
模块模式——你在此陈述你是想要用户模式还是内核模式(或者其他的超出此教程,如VSH)。
历史上,在许多程序里使用数字值。清晰起见,也许你想用稍详细点儿的:enum PspModuleInfoAttr
{
PSP_MODULE_USER = 0,
PSP_MODULE_KERNEL = 0X1000,
};
复制代码6.1.2 主线程 还是 不要主线程?
就像前面讨论过的,你需要决定是否要一个新线程(不清楚就是要)。如果不需要/需要的话,可以使用宏PSP_NO_CREATE_MAIN_THREAD()
复制代码就行了。相反,如果你想要主线程,你可以扭转几个参数,使用以下宏:PSP_MAIN_THREAD_PRIORITY(priority)
PSP_MAIN_THREAD_STACK_SIZE_KB(size_kb) \
PSP_MAIN_THREAD_ATTR(attr)
PSP_MAIN_THREAD_NAME(S)
复制代码或者使用一个单一的。#define PSP_MAIN_THREAD_PARAMS(priority, size_kb, attribute) \
PSP_MAIN_THREAD_PRIORITY(priority);\
PSP_MAIN_THREAD_STACK_SIZE_KB(size_kb);\
PSP_MAIN_THREAD_ATTR(