首页
社区
课程
招聘
[原创]打造柚子(yuzu)模拟器的金手指工具
发表于: 2024-3-14 13:13 15068

[原创]打造柚子(yuzu)模拟器的金手指工具

2024-3-14 13:13
15068

最近比较令人意外的事情是柚子模拟器因为被任天堂起诉而关闭,所以后续switch模拟器如何发展还很不好说。不过就如世界每天都有各种灾难,而我们的生活依然要继续一样,柚子模拟器关闭了,但我们的“学习”还得继续。(目前最新的柚子模拟器代码我恰好在关闭前同步更新了,master主干是柚子官方关闭前的代码,AddMemorySniffer分支则是我进行的修改,只是之前还想着定期要同步一下,以后就只能看yuzu-mirror(已被ban)或者suyu-emu(gitlab被ban后自己布了服务器)这个能不能起来了)

这次我要说的是与金手指相关的事,也就是关于游戏修改的事情。

一、柚子模拟器的部分机制
柚子模拟器用来模拟cpu的模块叫dynarmic,我本来以为它是一个第三方组件,结果柚子关闭后,这个工程的github也关闭了,才发现这部分就是柚子团队开发的。。

基于冯.诺依曼架构的计算机,核心思想就是存储程序的思想,所以存储是核心,代码与数据都保存在存储器中,而金手指对游戏的修改,表面上看都是对内存的修改,实际上则是有2类,一类是数据,一类则是代码(当然从修改的角度看,都是改的数据,因为程序也是存储起来的嘛)

1、内存

柚子模拟器的core模块里有一个类叫Memory,看起来实现了内存的读写,然后dynarmic组件需要外部提供一个回调类,主要方法就是内存读写。

那么模拟器的内存读写都是通过Memory类来实现的吗?想像一下arm指令集,大部分指令都涉及对内存的读或写,如果都要通过回调来实现,是不是感觉特别慢呢(其实我一开始也被这个回调误导了很久)。

dynarmic里的vm在执行arm指令时,并不是通过Memory类来实现的,它其实是模拟了cpu的分页式存储机制,有个页表来直接进行地址的转换,然后vm就直接操作内存了(这在dynarmic里被称为inline page tables优化,意思可能是直接内联了页表里的内存指针)。这样效率就比较高,不过为了支持调试,比如内存断点,柚子也模拟了硬件内存断点的基础机制,这个机制是Memory实现的

如果一块内存被标记为debug了,那么vm对该内存的操作就会改为调用前面的回调接口的内存读写方法。然后我们在Memory的读写方法里hook进一些代码就可以实现类似内存断点的调试了。

2、jit

dynarmic的主要功能是一个jit虚拟机,这个模块主要分为前端与后端2大块,前端主要是arm指令的解码与IR基本块的构建,arm指令有标准规范,IR则是dynarmic自己设计的,所以其实还有一块是IR。前端其实有点像是逆向的流程,从arm二进制代码逆向出基于IR的程序结构(可以认为是由基本块构成的程序流图)。后端是将前端的程序结构翻译到host的指令代码(当然,其实就是一块内存数据)。在dynarmic里,这几部分的名称是decoder、translate、emit与jit(dynarmic里面jit所在文件名叫interface,可能是因为jit vm也是这个模块的对外接口的原因)。

前面说的其实是一个jit虚拟机的高层结构,可能不同的虚拟机都类似,所以这些其实不是重点。虚拟机的大致执行流程如下图上半部分所示。
dynarmic jit虚拟机

上面的图的下半部分是后端与vm执行的关键点。

vm是按pc来执行代码的,对于arm64指令,每条指令都是4字节长度。形象的看vm就是一条指令接一条指令的解释执行,但很显然,这样效率太低了。

实际上dynarmic虚拟机在程序入口时发起执行调用,然后jit执行相关代码,并不会每条指令执行后都返回到调用方,也就是vm其实并不是一条指令一条指令的解释执行。最起码,对于一个基本块,这些指令完全可以一次由cpu执行完;当然这还远远不够,jit会分析基本块之间的链接关系,如果下一个基本块的地址在jit时是确定的,那么也不需要返回到调用方再重新发起jit执行调用,对于返回指令也有类似优化,这在dynarmic里分别叫block linking优化与return stack buffer(RSB)优化(RSB是intel cpu的特性,AMD cpu的叫RAS--return address stack)。与直接跳转到目的地址执行有关的优化还有一个是fast dispather,这个采取2层dispatch,使用一个MRU cache来查找跳转目标。

jit总有返回到调用方的时候,这些情形是jit无法继续处理的时候,这与cpu很类似,包括系统调用、中断与异常、内存地址无效(虚拟内存映射到物理内存)、以及与调试功能相关的断点、单步执行等。

这种运行方式主要就是为了执行效率,要不然游戏就该跑不动了。但同时这种方式也让调试分析变得比较麻烦,比如我们想hook每次过程调用,就没有合适的hook点。

所以我们需要对dynarmic的机制做些改造。

3、调试支持

柚子模拟器实现了gdb server的协议(这部分代码在core/debugger目录下,gdbstub.h/cpp实现了协议),所以是支持gdb远程调试的,然后idapro可以使用gdb server的协议远程调试,这就实现了调试的闭环。

调试除了查看线程状态如寄存器、堆栈外,最主要的就是中断执行过程,而模拟器作为解释器,是比较容易实现中断执行的。对jit要复杂一些,其实就是前面提到的jit优化正好是不利于调试的,所以调试时需要关掉一些jit优化,柚子的单步执行就是这样的。

所以对模拟器来说,支持调试可能没那么复杂,柚子在jit vm上实现了单步执行,然后支持了断点指令brk,再加上内存的调试属性与读写回调,基本就实现了gdb能支持的所有调试功能。(我很惊讶ryujinx好像是不支持调试的,不知道作者出于什么原因放弃了这个功能)

二、对jit的改造
1、需求

我们其实需要一些与调试类似的功能,比如我希望能记录某个时刻调用过哪些函数。暂停然后单步运行是可以的,但是效率可能太低了,短时间可能会执行了几百个上千个的函数,而指令可能是几万条,一个一个的看对眼睛与大脑都是很大的考验。而且单步执行就会打断游戏的UI,这样就很难跟踪一个具体操作对应的代码了。

2、关闭一些优化

我们前面说过有三个优化是不利于调试的,所以首先需要关掉它们:block linking、return stack buffer(RSB)、fast dispatcher。这三个优化都在设置的debug下的cpu页签里。首先还需要打开一个总的开关才能修改,这个开关也在debug设置里。

3、修改jit的框架指令

框架指令共有6种:

(RunCode与StepCode的函数原型:第一个参数实际是A64JitState的指针

a、runcode

这是一个基本块的jit代码的开始框架,我们只看简化配置下的流程,首先使用r15记录了JitState,然后将要执行的代码赋给rbx,然后判断halt_reason是否不为0(就是单步、中断、异常等状态),如果不为0则直接返回,否则跳转到要执行的代码。看起来似乎没什么奇怪的地方。

runcode代码在调用VM的Run方法时使用(我们说的返回到VM调用方,就是这个Run函数正常返回):

b、stepcode

这是一个基本块的单步执行jit代码的开始框架,这个与非单步的代码的差别主要是给halt_reason加上了Step状态。似乎也没啥特别的地方,不过它启发我们加上halt_reason似乎就有机会在指令执行后返回到VM调用方,但是并没有这么简单,我们看到Arm指令反编译到IR时,对单步还有一些处理

可以看到对于单步执行,执行完一条指令后,会再执行一条IR::Term::LinkBlock指令。

stepcode在调用VM的Step时使用(我们说的返回到VM调用方,在单步时就是这个Step函数正常返回):

c、returnfromruncode

这段代码用于从jit代码跳转到dispatch的代码,也就是根据PC跳到PC对应的jit代码的地方。我们比较关心的是与单步执行有关的一些IR的翻译,有4个指令会判断单步标志

我们可以看到在单步执行时,除RSB与FastDispatch外,都是修改JitState的PC值为指令对应的PC值,然后执行returnfromruncode,而returnfromruncode首先判断halt_reason是否为0,不为0则跳到返回到VM调用方的代码,为0则调用Dynarmic::A64::Jit::Impl::GetCurrentBlockThunk(由cb.LookupBlock->EmitCall(*this);生成)来获取下一条指令的jit代码入口,然后跳到jit代码入口执行(这里不是使用runcode框架代码来运行的jit代码,这就是我们调用Run的时候并不能一条指令一条指令执行的最主要的原因,优化也是在此基础上的优化)

d、returnfromruncode+MXCSR_ALREADY_EXITED

如后面所说,这个只在指令解码失败需要解释执行该指令时出现,我们略过就好。

e、return_to_caller

f、return_to_caller_mxcsr_already_exited

这是正常返回调用方的流程,jit指令如果能跳转到return_to_caller或return_to_caller_mxcsr_already_exited,就会返回到VM调用方,也就是前面提到的Run/Step的调用方。

==【一些补充注解】==

注1:

下面是与Mxcsr相关的2个函数,可以看到是保存与恢复MXCSR状态寄存器的。所以return_to_caller与return_to_caller_mxcsr_already_exited的差别是前者多了一步恢复MXCSR的操作。目前实际会出现MXCSR_ALREADY_EXITED的情形只有一种是指令Decode失败后委托给使用方来解释执行该指令的时候。所以我们分析时可以忽略这个情形。

注2:

rcp生成的指令是使用r14与r13保存页表与fastmem_pointer,与我们要解决的问题关系不大。

注3:

ABI_PushCalleeSaveRegistersAndAdjustStack与ABI_PopCalleeSaveRegistersAndAdjustStack主要是对寄存器的保存与恢复,类似编译高级语言生成的prologue与epilogue代码。我们也不用太关注。

注4:

查找指令jit代码入口的指令生成,这句话生成对Dynarmic::A64::Jit::Impl::GetCurrentBlockThunk的调用,用以获取当前PC对应的jit指令入口(所以在执行到这里时当前PC需要调整为合适的值)

==【补充注解结束】==

从这些代码看,如果关掉了我们之前提到过的3个优化后,似乎只要在Run函数里也像Step函数一样把halt_reason设置上就可以让每次执行jit代码后返回到VM调用方了。对单步执行,的确是这样的,因为每条指令都是从Step调用来的,然后都标记了halt_reason,执行完一条指令后就会返回到VM调用方,然后继续执行下一条指令,如此循环。

但是,如果不是想单步(我们显然不是要重复实现一个已经有的功能,我们想要的是一个比较高效的能在游戏过程中抓数据的机制),这里就比较微妙了。假设我们只想在每次分支指令执行后返回到VM调用方,也就是每个基本块的入口执行后返回。那对于一个由多条指令组成的基本块,我们就不能在Run开始就标记上halt_reason(否则第一条指令就返回了),但一旦我们没有标记,按照前面看到的执行机制,除非遇到中断或异常,执行是不会返回到调用方的。

这里涉及两件事,一件事是我们看来需要根据指令是什么来决定要不要标记halt_reason(因为我们不能使用Step来标记halt_reason,我们还需要选择一个状态值,dynarmic提供了几个预留的值供外部使用,柚子模拟器已经用了几个,不过还有剩余,比如UserDefined1就没有被用)。我们考虑加上两类条件过滤:

a、首先我们可以限定一下指令范围,比如限定到main模块的范围。

b、然后我们通常关心的是流程发生跳转的时候,也就是过程调用、跳转(条件或无条件)、过程返回这类指令。

想限定这些我们就需要知道jit代码入口对应的Arm PC与指令码,这个在jit之后已经没有对应了,所以我们还需要修改一下emit部分,在发出每个基本块的指令时,在指令前空出16个字节来,8个用于保存PC,然后4个字节用于对齐或预留给将来,再4个字节保存arm指令码。

这样数据就准备好了,然后我们在jit框架代码里加上对PC与指令码的判断,选择性加上HaltReason即可。(jit框架代码本身也是jit出的host指令,所以这部分相当于写点汇编代码)

我们先写个通用的判断与跳转:

然后第一件事只需要修改jit框架代码runcode:

第二件事是对于我们没有标记halt_reason的指令,因为它们不返回,在它们之后的分支指令也会跟着一块跑飞。所以我们还需要一个办法让跑飞的代码在遇到分支指令时能返回到VM调用方。这主要是需要在runfromruncode的时候进行处理(我们的前提是已经关闭了3个优化),也就是在从一个基本块跳到另一个基本块的dispatch代码这里,我们把dispatch代码稍做修改,再次根据PC与指令做一次判断,然后对需要返回的,跳转到返回的jit框架代码,而不是进行dispatch(dispatch是指根据PC取jit代码然后跳转到jit代码入口)

这个需要修改2个框架代码:

a、runfromruncode

b、runfromruncode+MXCSR_ALREADY_EXITED

这样我们就实现了在模拟器执行分支指令时返回到VM调用方的机制。当然,并不是只有分支指令时会返回,因为中断、异常的时候也会返回到VM调用方。

三、内存数据修改工具
0、命令行样式命令与函数样式命令

我们为柚子添加的命令有两类,一类就是通常意义的命令行样式的命令,为了简单,这种形式的命令只处理没有参数或有一个参数的情形。另一类则是函数调用样式的命令,这种可以有多个参数,并且参数是有类型的,字符串需要用引号括起来。由于命令与函数添加的越来越多,我加了一个help命令用来列出所有命令与函数,help命令可以带一个参数,参数用来过滤命令与函数列表。

使用help命令获取符合条件的命令与函数列表

有几个特殊的函数样式的命令用于实现批量命令

(我使用MetaDSL语法来实现脚本命令,与c语言最大的不同是多个函数间需要用分号分隔(当前块的最后一个语句可以不加),即便某个函数是大括号结尾也需要加分号)

比如,我可以这样一次执行2条命令(这种情形使用cmd效果相同):

1、内存搜索、查看与修改命令

为了方便使用,内存搜索命令尽量使用比较少的参数。我们添加的内存搜索相关的命令主要有:

这里就简单列一下命令,后面说到游戏修改实例时再看如何使用。

2、内存快照与比对功能

这部分主要使用ui操作。

内存快照与比对功能ui

通常的操作步骤如下:

a、我们首先勾选Enable,此时信息列表窗口会显示当前运行的游戏的内存段信息,同时如果我们没有输入Start/Size/Step的话会默认使用heap的起始地址,size会设置为一个固定的值0x10000000,step设为4。

b、根据需要修改Start/Size/Step,这是稍后要用到的内存搜索范围与搜索步长(同时也是搜索值的size)。

c、如果我们有一个比较明确的值要搜索,可以输入Value。

d、点击AddSniffing按钮,此时将在指定内存范围内搜索Value,搜索到的内存地址将添加到监听内存地址列表中。如果未指定Value时将指定内存范围按Step大小拆分成若干监听内存地址并添加到列表,所以不指定Value时内存范围不要太大,否则不只操作慢,还会耗费大量内存。

e、此时继续游戏一会。

f、如果我们想要监听的游戏数据没有变化,点Keep Unchanged按钮,否则按实际情况点另外3个Keep按钮。此时会对监听地址列表进行过滤,保留符合条件的内存地址,并显示前10个地址到信息列表窗口。

g、如果看到当前内存地址数量已经少到我们可以分析了,就可以停止操作了。

h、有时候我们可能找不到符合条件的内存地址,此时可以使用Rollback/Unrollback按钮回退/前进到最近的Keep结果。因为我们要保存每次Keep操作的结果,所以这里也会占比较多的内存。

i、最理想的状态是,我们看到最后几次Keep结果里的内存地址的数据正好与我们在游戏里看到的一样,这表明我们已经找到了保存游戏数据的内存,一般就可以直接修改了。

j、如果我们最后只有几条数据并且这些数据正是我们想要锁定的数据,那么我们可以点击SaveAbs或SaveRel来保存一个金手指文件,这个金手指文件使用绝对地址或相对地址的指令来锁定这些内存值(不做条件判断,每个tick都重新写入)。

AddSniffing按钮的ui功能相对较弱,我们也提供了两个函数样式的命令来添加内存监听地址

a、addsniffing(addr,size[,step,val])
这个函数与ui的操作是一样的,因为是命令,所以可以批量处理添加多个内存范围或地址。
b、addsniffingfromsearch(find_vals)
这个函数与内存搜索命令相似,它也使用内存搜索的其它参数,不过它将搜索到的结果内存(是一个与setmemrange指定的大小相同的区域)添加到监听内存列表。
3、内存调试日志

内存调试日志其实是一个类似数据断点的功能,我们指定了要调试的内存地址后,每次这些地址被读/写/指针访问时,就会记录当时的调用栈与执行上下文的信息。这些信息显示在ui上,我们可以用命令保存到文件。

所有命令里的addr都可以是十进制或十六进制表示(0x开始)

这里也只简单列一下命令,本文的游戏修改实例未涉及这些命令的使用。

四、内存指令修改工具
1、指令日志与比对

指令日志用来跟踪程序执行了哪些指令,为了避免游戏卡死,指令日志只记录pc值。使用此功能前,我们需要关掉block linking、RSB与fast dispatcher三个优化。也可以指定哪些指令记录到日志。

也有一个函数样式的命令用来增加一个更具体的分支指令到记录条件

当 (指令码 & mask) == value 时,这条分支指令会被记录
下面是指令日志与比对的命令

2、断点与单步跟踪

单步跟踪主要用来记录单步执行时每条指令的调用栈与环境信息。断点用来记录执行到断点时的调用栈与环境信息。

断点与单步跟踪的信息也记录在trace buffer里,所以下面2条命令也影响这块。

3、指令读写(同内存修改)

五、可编译到金手指的脚本
柚子模拟器支持的金手指代码是一个小型的虚拟机指令集(目前有十多条指令),由于是指令集样式的,它采用位编码来指明各种信息,这样手写起来就和人工翻译汇编代码到机器指令一样。

我就想能不能把我们已经支持的命令脚本用在这呢,或者说提供一个编译/汇编器,把命令脚本翻译为金手指指令。

这通常有两种方式,一种是标准的编译器做法,我们定义好高层的语言,然后编译到目标代码。另一种则是把输出目标代码看成脚本代码的输出,这样其实就是为脚本添加API就可以了,只不过一般的脚本API都是函数或对象的形式,不太适合表达if/else或语句块这样的嵌套语法。不过我们的命令脚本是基于MetaDSL的,它的特点就是支持语句与块形式的API语法。所以我就采用了后面这种做法。

所以我们的金手指脚本其实是加了一些命令,但语法上与一个脚本语言没什么差别,下面我们就来看看这个脚本语言。

1、语句

dmnt_file()

{

//金手指脚本代码
};

dmnt_if()

{
}
elif/elseif()

{
}
else

{
};

dmnt_loop()

{
};

2、函数

这文章写的太长了,偷偷懒就把文档贴这了:)

这些函数与金手指基本上是对应的,有些参数我就不解释了,参阅金手指文档吧:)

金手指指令文档

3、实例

估计多数普通的金手指都不需要使用上面这一堆命令,如果是简单修改内存就能办到的话,修改下面这个金手指文件通常就可以搞定了。

这个是 伊苏X 1.05 的一个金手指脚本,主要需要修改5处内存,都是4字节。内存地址位于main模块。需要更多的内存就复制增加新的就好了。

@base 是main模块的基地址,如果不是main模块按需要修改。

@addr1~@addr5是需要修改的内存地址。

@wval1~@wval5是希望修改成的值。

这个脚本的逻辑,是对每个内存地址,先读取其值看是否是期望的值,如果不是则写入。如果我们是在修改好的游戏上运行,@wval1~@wval5可以置成0,此时脚本会读取当前游戏在这些内存的值作为期望值。

写入数据用的指令是Store Static Value to Memory,对应我们脚本的函数是dmnt_store_v2a,金手指写到内存地址也需要一个寄存器保存offset,所以我们用dmnt_load_v2r函数给0号寄存器赋0来作为寄存器偏移。然后我们就把实际偏移作为立即数(@offset1~@offset5)提供就可以了。

最后再说一遍,这个脚本需要特别注意的地方,是每个语句结束的大括号后要加分号(最后一个语句可以不加)。

上面文件生成的金手指文件如下,如果你正好用的是伊苏X 1.05版本,这个就作为看这么长的文章的福利拿去用好了:)

主要功能:

a、每次加载时背包里所有物品(除DLC)与金钱全满(超上限)

b、打死怪物时直接满级(记不太清是打死还是升级时了)

c、使用DLC物品加基础属性时,直接加满(超上限)。

文件名:7E06539B5874B9C4.txt

4、柚子模拟器的金手指目录结构

使用ui上的Run Script按钮,选择这个脚本文件,然后会在yuzu.exe所在的目录下生成一个金手指文件,文件名是当前游戏main模块的build_id。

金手指文件在柚子里目录结构应该如下:

其中title_id是游戏的一个标识,build_id是我们修改的模块的build_id。在游戏没有运行时,右键点游戏图标,选Open Mod Data Location,就会打开游戏的[title_id]目录,然后在下面按目录结构创建目录,最后把yuzu.exe目录下的[build_id].txt拷到cheats目录下,然后再在游戏图标上右键选Properties,应该就多了一个金手指模块了,勾选并启动游戏,金手指就用上了。

六、switch游戏修改实例
1、星之海洋——第二个故事R

这个介绍一种最简单的改法,用winhex或hxd直接打开存档文件,搜索人物属性和钱的十六进制字节数据就能找到,其实都不用搜索,第一屏直接能看到这些数据都在一块:)改完保存一下,然后在游戏里加载就可以看到已经变成修改后的数据了。

金钱等级生命魔法
金钱等级生命魔法
各种属性
各种属性
所以这个游戏都用不着我们前面说的各种工具了。

2、塞尔达王国之泪1.2

这个需要使用内存搜索功能。背包里的物品数量、金钱都可以修改。这个我没有尝试改存档,没准也可以。

这个我主要使用searchmem命令来查找的,王国之泪的数据存在堆上,这个的地址范围比较大,可能搜索需要些时间。下面是搜索到的2个符合的内存区域,search result是列出的我们要找的数据与发现它们的地址。area memory则是发现这些数据的连续内存区域的数据,可以通过看其它数据来确认是否是要找的内存。

这2块内存都是,所以我们把它们都修改一下,命令如下(王国之泪1.2版本应该直接可用):

执行后,物品背包的物品就都变成386个了,可能得关闭了重新打开一下,数据变化后最好存一下盘。
物品背包

类似的,我们也能找到另外几个页签的数据的保存地址,这里就不重复列了,为了感谢您看了这么久的文章,我直接给出修改命令吧。

左纳乌背包:

左纳乌背包

弓箭:

弓箭与金币

金币:

地图魂魄等:

地图魂魄等

食物等:

食物

食物这里比较奇怪的是,有些食物背包只能放一个,我们改了后,看起来也仍然是一个,但消耗掉一个后,关了背包再打开,发现又有了,我怀疑它其实真的存了多个了:)

3、荒野大镖客1

这个需要使用内存快照与比对功能,我们只修改金钱,这里给出一种在轮盘赌的时候修改金钱的办法。手上有10块钱的时候就可以去轮盘赌了,初始会有100个筹码,在轮盘赌开始后,筹码为100时,我们添加内存监听,监听main模块里4字节数值为100的内存地址。
监听main模块里4字节值为100的内存

然后开始赌,每次扣筹码后,点击Keep Change(或者用Keep Decreased,应该会更快收敛)。大约5、6次后就能确定保存筹码的内存了。
根据筹码数值变化能直接看到对应的内存了

这样我们就看到是在内存地址0x874aac68保存的筹码(这个地址不是固定的,每次运行可能不一样),然后我们用命令把它改成1000000

再接着下注一次,会看到筹码变成了一串0,表明改成功了,退出轮盘赌存盘即可。
金钱与来源统计

整个操作过程的信息列表窗口内容如下:

4、伊苏十 1.05

这个游戏有一些防修改与加密的措施,改存档会报错(估计是有crc校验一类),改内存里的数据好像也会(我在heap与main没搜到,alias段没试,后来在星之海洋里发现原来alias这个段好像也被游戏用来存数据了,有可能还是switch上游戏的惯用法)。

所以这个需要祭出神器ida pro来,与我们的改造相比,这才是真正的神器哈,怎么用我就不说了,这玩意不太简单呢。前面说过,柚子支持gdb server的调试协议,而ida pro就可以用gdb server协议来远程调试,最主要是它还支持arm指令的调试,这非常难得。

为了能调试,我们首先需要把xci或nsp里的main解出来,这个可以用(也是神器哈)

https://github.com/Myster-Tee/NxFileViewer ​github.com/Myster-Tee/NxFileViewer

然后,main要能用ida pro打开,还得把它变成elf文件(也有ida pro的插件能直接加载nso文件的),这个可以用这个工具(switch神器太多了啊)

https://archive.org/download/nx2elf2nso/nx2elf2nso.zip
​archive.org/download/nx2elf2nso/nx2elf2nso.zip

拿到main.elf文件后,我们就可以用ida pro打开,然后远程连接柚子模拟器调试了。

调试细节就不说了,这文章实在太长了。。

经过一番折腾后,我找到了几个修改点,然后写了前面的金手指工具来生成伊苏X 1.05的金手指,前面已经贴过了,别说你是跳到这的哈~

作为实例,我这里想说的是如何找到使用DLC物品加基础属性的代码点的,这里用的是前面打造的pc记录功能。

a、关闭模拟器的三个优化
先勾上这个
先勾上这个

然后关掉这三个
图片描述

b、运行游戏,然后打开背包,找到加基础属性比如体力的DLC物品(假设大家用的是带DLC的那个版本),先不要点确认。

c、执行pc记录的一些命令

d、什么也不要做让游戏跑一会,然后输入命令

把之前的pc记录暂存成一个参照

e、现在在游戏里点确认,然后输入命令

生成点确认后新记录的pc

f、输入命令保存结果,文件默认放在yuzu.exe所在目录(如果我们没有指明文件路径的话)

h、关闭pc记录

这就完成了一次与操作相关的pc记录。

然后我们可以多记录几次,得到多个pc记录文件,之后我们可以用文本对比工具,得到多个pc记录里相同的部分,把它们拷出来。

现在需要在ida pro里做些处理了。

a、首先我们打开柚子的远程调试配置
勾上远程调试配置
图片描述

b、重启游戏,会等待连接调试器,我们连接上ida pro。

c、前面比对得到了几次记录的公共记录,我们把其中的地址拷出来,写到一个假设叫addbp.py的python文件,然后把每个地址变成一个python函数调用(地址是列对齐的,所以使用列编辑功能很容易修改,或者正则表达式替换):

我实际得到的地址一共有500多个呢,不用脚本可能会有点难。

d、现在在ida pro里执行上面的addbp.py脚本,这些地址就都加上断点了。

e、现在我们来写一个ida pro的调试脚本,功能是在每个断点断下来后自动删除这个断点,假设保存成python脚本文件dbghook_remove_bp.py

f、现在在ida pro里执行上面的脚本dbghook_remove_bp.py,然后继续运行游戏,并再次打开物品栏,并选中加体力的DLC物品,记得不要点确认。

g、现在让ida pro的脚本跑一会,等它不再断点的时候,我们在底部的python命令那输入下面脚本代码

这个断点删除脚本就算结束了。

h、我们现在应该还剩余一些断点,我们再来写另一个ida pro的调试脚本,功能是执行到断点时记录一些断点的信息,假设保存为python文件dbghook_log_bp.py

i、我们现在在ida pro里强制断下游戏,然后运行上面的脚本dbghook_log_bp.py,再继续游戏并点游戏里使用DLC物品的确认,应该会看到有一些断点被断下又继续了

j、再次强制断下游戏,我们把ida pro里刚才脚本的输出拷出来,找个文本文件保存,看看有没有我们感兴趣的数据吧(刚才的DLC物品加基础属性,每次加3点),如果断点寄存器里有相关信息,那对应的断点地址应该是比较关键的地址了,那就去看这段代码就好。

k、如果没找到什么有用的信息,那我们看一下断点记录的第一个断点与最后一个断点,如果汇编代码不是特别多的话,我们可以使用ida pro的tracing功能,然后在第一个断点处开始trace,再次在游戏里操作一次。

l、然后把tracing的结果保存下来,仔细看看这部分吧,没别的技巧了,一般会有发现的。

我们在tracing记录里找找3.0之类的数据,然后看看它后面的代码,然后应该会看到加3的代码(不是直接加3,是加一个寄存器,而之前3赋给了寄存器),这就是我们要找的关键代码了,改一下这段逻辑就可以了。

这里我们找到的修改点有2个,就是我们之前金手指文件5个地址的后2个。

修改后,我们就得到了这样的游戏数值
属性等级

物品数量

======

终于写完了啊,长输一口气~

struct UserCallbacks {
    virtual ~UserCallbacks() = default;
 
    // All reads through this callback are 4-byte aligned.
    // Memory must be interpreted as little endian.
    virtual std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); }
 
    // Reads through these callbacks may not be aligned.
    virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0;
    virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0;
    virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0;
    virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0;
    virtual Vector MemoryRead128(VAddr vaddr) = 0;
 
    // Writes through these callbacks may not be aligned.
    virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0;
    virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0;
    virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0;
    virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0;
    virtual void MemoryWrite128(VAddr vaddr, Vector value) = 0;
 
    // Writes through these callbacks may not be aligned.
    virtual bool MemoryWriteExclusive8(VAddr /*vaddr*/, std::uint8_t /*value*/, std::uint8_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive16(VAddr /*vaddr*/, std::uint16_t /*value*/, std::uint16_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive32(VAddr /*vaddr*/, std::uint32_t /*value*/, std::uint32_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive64(VAddr /*vaddr*/, std::uint64_t /*value*/, std::uint64_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive128(VAddr /*vaddr*/, Vector /*value*/, Vector /*expected*/) { return false; }
 
    // If this callback returns true, the JIT will assume MemoryRead* callbacks will always
    // return the same value at any point in time for this vaddr. The JIT may use this information
    // in optimizations.
    // A conservative implementation that always returns false is safe.
    virtual bool IsReadOnlyMemory(VAddr /*vaddr*/) { return false; }
 
    /// The interpreter must execute exactly num_instructions starting from PC.
    virtual void InterpreterFallback(VAddr pc, size_t num_instructions) = 0;
 
    // This callback is called whenever a SVC instruction is executed.
    virtual void CallSVC(std::uint32_t swi) = 0;
 
    virtual void ExceptionRaised(VAddr pc, Exception exception) = 0;
    virtual void DataCacheOperationRaised(DataCacheOperation /*op*/, VAddr /*value*/) {}
    virtual void InstructionCacheOperationRaised(InstructionCacheOperation /*op*/, VAddr /*value*/) {}
    virtual void InstructionSynchronizationBarrierRaised() {}
 
    // Timing-related callbacks
    // ticks ticks have passed
    virtual void AddTicks(std::uint64_t ticks) = 0;
    // How many more ticks am I allowed to execute?
    virtual std::uint64_t GetTicksRemaining() = 0;
    // Get value in the emulated counter-timer physical count register.
    virtual std::uint64_t GetCNTPCT() = 0;
};
struct UserCallbacks {
    virtual ~UserCallbacks() = default;
 
    // All reads through this callback are 4-byte aligned.
    // Memory must be interpreted as little endian.
    virtual std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); }
 
    // Reads through these callbacks may not be aligned.
    virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0;
    virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0;
    virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0;
    virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0;
    virtual Vector MemoryRead128(VAddr vaddr) = 0;
 
    // Writes through these callbacks may not be aligned.
    virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0;
    virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0;
    virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0;
    virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0;
    virtual void MemoryWrite128(VAddr vaddr, Vector value) = 0;
 
    // Writes through these callbacks may not be aligned.
    virtual bool MemoryWriteExclusive8(VAddr /*vaddr*/, std::uint8_t /*value*/, std::uint8_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive16(VAddr /*vaddr*/, std::uint16_t /*value*/, std::uint16_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive32(VAddr /*vaddr*/, std::uint32_t /*value*/, std::uint32_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive64(VAddr /*vaddr*/, std::uint64_t /*value*/, std::uint64_t /*expected*/) { return false; }
    virtual bool MemoryWriteExclusive128(VAddr /*vaddr*/, Vector /*value*/, Vector /*expected*/) { return false; }
 
    // If this callback returns true, the JIT will assume MemoryRead* callbacks will always
    // return the same value at any point in time for this vaddr. The JIT may use this information
    // in optimizations.
    // A conservative implementation that always returns false is safe.
    virtual bool IsReadOnlyMemory(VAddr /*vaddr*/) { return false; }
 
    /// The interpreter must execute exactly num_instructions starting from PC.
    virtual void InterpreterFallback(VAddr pc, size_t num_instructions) = 0;
 
    // This callback is called whenever a SVC instruction is executed.
    virtual void CallSVC(std::uint32_t swi) = 0;
 
    virtual void ExceptionRaised(VAddr pc, Exception exception) = 0;
    virtual void DataCacheOperationRaised(DataCacheOperation /*op*/, VAddr /*value*/) {}
    virtual void InstructionCacheOperationRaised(InstructionCacheOperation /*op*/, VAddr /*value*/) {}
    virtual void InstructionSynchronizationBarrierRaised() {}
 
    // Timing-related callbacks
    // ticks ticks have passed
    virtual void AddTicks(std::uint64_t ticks) = 0;
    // How many more ticks am I allowed to execute?
    virtual std::uint64_t GetTicksRemaining() = 0;
    // Get value in the emulated counter-timer physical count register.
    virtual std::uint64_t GetCNTPCT() = 0;
};
void MarkRegionDebug(u64 vaddr, u64 size, bool debug)
void MarkRegionDebug(u64 vaddr, u64 size, bool debug)
using RunCodeFuncType = HaltReason (*)(void*, CodePtr);
using RunCodeFuncType = HaltReason (*)(void*, CodePtr);
align();
run_code = getCurr<RunCodeFuncType>();
 
// This serves two purposes:
// 1. It saves all the registers we as a callee need to save.
// 2. It aligns the stack so that the code the JIT emits can assume
//    that the stack is appropriately aligned for CALLs.
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
mov(rbx, ABI_PARAM2);  // save temporarily in non-volatile register
 
if (cb.enable_cycle_counting) {
    cb.GetTicksRemaining->EmitCall(*this);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
 
SwitchMxcsrOnEntry();
jmp(rbx);
align();
run_code = getCurr<RunCodeFuncType>();
 
// This serves two purposes:
// 1. It saves all the registers we as a callee need to save.
// 2. It aligns the stack so that the code the JIT emits can assume
//    that the stack is appropriately aligned for CALLs.
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
mov(rbx, ABI_PARAM2);  // save temporarily in non-volatile register
 
if (cb.enable_cycle_counting) {
    cb.GetTicksRemaining->EmitCall(*this);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
 
SwitchMxcsrOnEntry();
jmp(rbx);
HaltReason Run() {
    ASSERT(!is_executing);
    PerformRequestedCacheInvalidation(static_cast<HaltReason>(Atomic::Load(&jit_state.halt_reason)));
 
    is_executing = true;
    SCOPE_EXIT {
        this->is_executing = false;
    };
 
    // TODO: Check code alignment
 
    const CodePtr current_code_ptr = [this] {
        // RSB optimization
        const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask;
        if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
            jit_state.rsb_ptr = new_rsb_ptr;
            return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]);
        }
 
        return GetCurrentBlock();
    }();
 
    const HaltReason hr = block_of_code.RunCode(&jit_state, current_code_ptr);
 
    PerformRequestedCacheInvalidation(hr);
 
    return hr;
}
HaltReason Run() {
    ASSERT(!is_executing);
    PerformRequestedCacheInvalidation(static_cast<HaltReason>(Atomic::Load(&jit_state.halt_reason)));
 
    is_executing = true;
    SCOPE_EXIT {
        this->is_executing = false;
    };
 
    // TODO: Check code alignment
 
    const CodePtr current_code_ptr = [this] {
        // RSB optimization
        const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask;
        if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
            jit_state.rsb_ptr = new_rsb_ptr;
            return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]);
        }
 
        return GetCurrentBlock();
    }();
 
    const HaltReason hr = block_of_code.RunCode(&jit_state, current_code_ptr);
 
    PerformRequestedCacheInvalidation(hr);
 
    return hr;
}
align();
step_code = getCurr<RunCodeFuncType>();
 
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
 
if (cb.enable_cycle_counting) {
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], 1);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 1);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
lock();
or_(dword[r15 + jsi.offsetof_halt_reason], static_cast<u32>(HaltReason::Step));
 
SwitchMxcsrOnEntry();
jmp(ABI_PARAM2);
align();
step_code = getCurr<RunCodeFuncType>();
 
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
 
if (cb.enable_cycle_counting) {
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], 1);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 1);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
lock();
or_(dword[r15 + jsi.offsetof_halt_reason], static_cast<u32>(HaltReason::Step));
 
SwitchMxcsrOnEntry();
jmp(ABI_PARAM2);
IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options) {
    const bool single_step = descriptor.SingleStepping();
 
    IR::Block block{descriptor};
    TranslatorVisitor visitor{block, descriptor, std::move(options)};
 
    bool should_continue = true;
    do {
        const u64 pc = visitor.ir.current_location->PC();
 
        if (const auto instruction = memory_read_code(pc)) {
            if (auto decoder = Decode<TranslatorVisitor>(*instruction)) {
                should_continue = decoder->get().call(visitor, *instruction);
            } else {
                should_continue = visitor.InterpretThisInstruction();
            }
        } else {
            should_continue = visitor.RaiseException(Exception::NoExecuteFault);
        }
 
        visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4);
        block.CycleCount()++;
    } while (should_continue && !single_step);
 
    if (single_step && should_continue) {
        visitor.ir.SetTerm(IR::Term::LinkBlock{*visitor.ir.current_location});
    }
 
    ASSERT_MSG(block.HasTerminal(), "Terminal has not been set");
 
    block.SetEndLocation(*visitor.ir.current_location);
 
    return block;
}
IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options) {
    const bool single_step = descriptor.SingleStepping();
 
    IR::Block block{descriptor};
    TranslatorVisitor visitor{block, descriptor, std::move(options)};
 
    bool should_continue = true;
    do {
        const u64 pc = visitor.ir.current_location->PC();
 
        if (const auto instruction = memory_read_code(pc)) {
            if (auto decoder = Decode<TranslatorVisitor>(*instruction)) {
                should_continue = decoder->get().call(visitor, *instruction);
            } else {
                should_continue = visitor.InterpretThisInstruction();
            }
        } else {
            should_continue = visitor.RaiseException(Exception::NoExecuteFault);
        }
 
        visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4);
        block.CycleCount()++;
    } while (should_continue && !single_step);
 
    if (single_step && should_continue) {
        visitor.ir.SetTerm(IR::Term::LinkBlock{*visitor.ir.current_location});
    }
 
    ASSERT_MSG(block.HasTerminal(), "Terminal has not been set");
 
    block.SetEndLocation(*visitor.ir.current_location);
 
    return block;
}
HaltReason Step() {
    ASSERT(!is_executing);
    PerformRequestedCacheInvalidation(static_cast<HaltReason>(Atomic::Load(&jit_state.halt_reason)));
 
    is_executing = true;
    SCOPE_EXIT {
        this->is_executing = false;
    };
 
    const HaltReason hr = block_of_code.StepCode(&jit_state, GetCurrentSingleStep());
 
    PerformRequestedCacheInvalidation(hr);
 
    return hr;
}
HaltReason Step() {
    ASSERT(!is_executing);
    PerformRequestedCacheInvalidation(static_cast<HaltReason>(Atomic::Load(&jit_state.halt_reason)));
 
    is_executing = true;
    SCOPE_EXIT {
        this->is_executing = false;
    };
 
    const HaltReason hr = block_of_code.StepCode(&jit_state, GetCurrentSingleStep());
 
    PerformRequestedCacheInvalidation(hr);
 
    return hr;
}
align();
return_from_run_code[0] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller);
}
cb.LookupBlock->EmitCall(*this);
jmp(ABI_RETURN);
align();
return_from_run_code[0] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller);
}
cb.LookupBlock->EmitCall(*this);
jmp(ABI_RETURN);
void A64EmitX64::EmitTerminalImpl(IR::Term::LinkBlock terminal, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) {
        code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
        code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
        code.ReturnFromRunCode();
        return;
    }
 
    if (conf.enable_cycle_counting) {
        code.cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
 
        patch_information[terminal.next].jg.push_back(code.getCurr());
        if (const auto next_bb = GetBasicBlock(terminal.next)) {
            EmitPatchJg(terminal.next, next_bb->entrypoint);
        } else {
            EmitPatchJg(terminal.next);
        }
    } else {
        code.cmp(dword[r15 + offsetof(A64JitState, halt_reason)], 0);
 
        patch_information[terminal.next].jz.push_back(code.getCurr());
        if (const auto next_bb = GetBasicBlock(terminal.next)) {
            EmitPatchJz(terminal.next, next_bb->entrypoint);
        } else {
            EmitPatchJz(terminal.next);
        }
    }
 
    code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
    code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
    code.ForceReturnFromRunCode();
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::LinkBlockFast terminal, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) {
        code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
        code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
        code.ReturnFromRunCode();
        return;
    }
 
    patch_information[terminal.next].jmp.push_back(code.getCurr());
    if (auto next_bb = GetBasicBlock(terminal.next)) {
        EmitPatchJmp(terminal.next, next_bb->entrypoint);
    } else {
        EmitPatchJmp(terminal.next);
    }
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::ReturnStackBuffer) || is_single_step) {
        code.ReturnFromRunCode();
        return;
    }
 
    code.jmp(terminal_handler_pop_rsb_hint);
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::FastDispatch) || is_single_step) {
        code.ReturnFromRunCode();
        return;
    }
 
    code.jmp(terminal_handler_fast_dispatch_hint);
}
void A64EmitX64::EmitTerminalImpl(IR::Term::LinkBlock terminal, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) {
        code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
        code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
        code.ReturnFromRunCode();
        return;
    }
 
    if (conf.enable_cycle_counting) {
        code.cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
 
        patch_information[terminal.next].jg.push_back(code.getCurr());
        if (const auto next_bb = GetBasicBlock(terminal.next)) {
            EmitPatchJg(terminal.next, next_bb->entrypoint);
        } else {
            EmitPatchJg(terminal.next);
        }
    } else {
        code.cmp(dword[r15 + offsetof(A64JitState, halt_reason)], 0);
 
        patch_information[terminal.next].jz.push_back(code.getCurr());
        if (const auto next_bb = GetBasicBlock(terminal.next)) {
            EmitPatchJz(terminal.next, next_bb->entrypoint);
        } else {
            EmitPatchJz(terminal.next);
        }
    }
 
    code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
    code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
    code.ForceReturnFromRunCode();
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::LinkBlockFast terminal, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) {
        code.mov(rax, A64::LocationDescriptor{terminal.next}.PC());
        code.mov(qword[r15 + offsetof(A64JitState, pc)], rax);
        code.ReturnFromRunCode();
        return;
    }
 
    patch_information[terminal.next].jmp.push_back(code.getCurr());
    if (auto next_bb = GetBasicBlock(terminal.next)) {
        EmitPatchJmp(terminal.next, next_bb->entrypoint);
    } else {
        EmitPatchJmp(terminal.next);
    }
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::ReturnStackBuffer) || is_single_step) {
        code.ReturnFromRunCode();
        return;
    }
 
    code.jmp(terminal_handler_pop_rsb_hint);
}
 
void A64EmitX64::EmitTerminalImpl(IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) {
    if (!conf.HasOptimization(OptimizationFlag::FastDispatch) || is_single_step) {
        code.ReturnFromRunCode();
        return;
    }
 
    code.jmp(terminal_handler_fast_dispatch_hint);
}
align();
return_from_run_code[MXCSR_ALREADY_EXITED] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller_mxcsr_already_exited);
}
SwitchMxcsrOnEntry();
cb.LookupBlock->EmitCall(*this);
jmp(ABI_RETURN);
align();
return_from_run_code[MXCSR_ALREADY_EXITED] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller_mxcsr_already_exited);
}
SwitchMxcsrOnEntry();
cb.LookupBlock->EmitCall(*this);
jmp(ABI_RETURN);
align();
return_from_run_code[FORCE_RETURN] = getCurr<const void*>();
L(return_to_caller);
 
SwitchMxcsrOnExit();
// fallthrough
 
return_from_run_code[MXCSR_ALREADY_EXITED | FORCE_RETURN] = getCurr<const void*>();
L(return_to_caller_mxcsr_already_exited);
 
if (cb.enable_cycle_counting) {
    cb.AddTicks->EmitCall(*this, [this](RegList param) {
        mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
        sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
    });
}
 
xor_(eax, eax);
lock();
xchg(dword[r15 + jsi.offsetof_halt_reason], eax);
 
ABI_PopCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
ret();
align();
return_from_run_code[FORCE_RETURN] = getCurr<const void*>();
L(return_to_caller);
 
SwitchMxcsrOnExit();
// fallthrough
 
return_from_run_code[MXCSR_ALREADY_EXITED | FORCE_RETURN] = getCurr<const void*>();
L(return_to_caller_mxcsr_already_exited);
 
if (cb.enable_cycle_counting) {
    cb.AddTicks->EmitCall(*this, [this](RegList param) {
        mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
        sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
    });
}
 
xor_(eax, eax);
lock();
xchg(dword[r15 + jsi.offsetof_halt_reason], eax);
 
ABI_PopCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
ret();
void BlockOfCode::SwitchMxcsrOnEntry() {
    stmxcsr(dword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, save_host_MXCSR)]);
    ldmxcsr(dword[r15 + jsi.offsetof_guest_MXCSR]);
}
 
void BlockOfCode::SwitchMxcsrOnExit() {
    stmxcsr(dword[r15 + jsi.offsetof_guest_MXCSR]);
    ldmxcsr(dword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, save_host_MXCSR)]);
}
void BlockOfCode::SwitchMxcsrOnEntry() {
    stmxcsr(dword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, save_host_MXCSR)]);
    ldmxcsr(dword[r15 + jsi.offsetof_guest_MXCSR]);
}
 
void BlockOfCode::SwitchMxcsrOnExit() {
    stmxcsr(dword[r15 + jsi.offsetof_guest_MXCSR]);
    ldmxcsr(dword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, save_host_MXCSR)]);
}
static std::function<void(BlockOfCode&)> GenRCP(const A64::UserConfig& conf) {
    return [conf](BlockOfCode& code) {
        if (conf.page_table) {
            code.mov(code.r14, mcl::bit_cast<u64>(conf.page_table));
        }
        if (conf.fastmem_pointer) {
            code.mov(code.r13, *conf.fastmem_pointer);
        }
    };
}
static std::function<void(BlockOfCode&)> GenRCP(const A64::UserConfig& conf) {
    return [conf](BlockOfCode& code) {
        if (conf.page_table) {
            code.mov(code.r14, mcl::bit_cast<u64>(conf.page_table));
        }
        if (conf.fastmem_pointer) {
            code.mov(code.r13, *conf.fastmem_pointer);
        }
    };
}
cb.LookupBlock->EmitCall(*this);
cb.LookupBlock->EmitCall(*this);
// Start emitting.
code.align();
code.dq(pc);
code.dd(0);
code.dd(firstArmInst);
const u8* const entrypoint = code.getCurr();
// Start emitting.
code.align();
code.dq(pc);
code.dd(0);
code.dd(firstArmInst);
const u8* const entrypoint = code.getCurr();
void BlockOfCode::GenHaltReasonSet(Xbyak::Label& run_code_entry) {
    Xbyak::Label _dummy;
    GenHaltReasonSetImpl(false, run_code_entry, _dummy);
}
void BlockOfCode::GenHaltReasonSet(Xbyak::Label& run_code_entry, Xbyak::Label& ret_code_entry) {
    GenHaltReasonSetImpl(true, run_code_entry, ret_code_entry);
}
void BlockOfCode::GenHaltReasonSetImpl(bool isRet, Xbyak::Label& run_code_entry, Xbyak::Label& ret_code_entry) {
    Xbyak::Label normal_code, halt_reason_set;
    if (halt_reason_on_run) {
        if (isRet) {
            push(ABI_RETURN);
            push(rbx);
            mov(rbx, ABI_RETURN);
        }
        push(rsi);
        push(rdi);
        push(r14);
        push(r13);
 
        mov(esi, word[rbx - 4]);
        mov(r14, dword[rbx - 16]);
 
        mov(r13, trace_scope_begin);
        cmp(r14, r13);
        jl(normal_code, T_NEAR);
        mov(r13, trace_scope_end);
        cmp(r14, r13);
        jge(normal_code, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfc000000);
        cmp(edi, 0x94000000);//BL
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd63f0000);//BLR
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffff800);
        cmp(edi, 0xd63f0800);//BLRxxx
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xff000010);
        cmp(edi, 0x54000000);//B.cond
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xff000010);
        cmp(edi, 0x54000010);//BC.cond
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x35000000);//CBNZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x34000000);//CBZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x37000000);//TBNZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x36000000);//TBZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfc000000);
        cmp(edi, 0x14000000);//B
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd61f0000);//BR
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffff800);
        cmp(edi, 0xd61f0800);//BRxxx
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd65f0000);//RET
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffbff);
        cmp(edi, 0xd65f0bff);//RETAA, RETAB
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xffc0001f);
        cmp(edi, 0x5500001f);//RETAASPPC, RETABSPPC
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffbe0);
        cmp(edi, 0xd65f0be0);//RETAASPPC, RETABSPPC
        jz(halt_reason_set, T_NEAR);
 
        L(normal_code);
        pop(r13);
        pop(r14);
        pop(rdi);
        pop(rsi);
        if (isRet) {
            pop(rbx);
            pop(ABI_RETURN);
        }
        jmp(run_code_entry, T_NEAR);
 
        L(halt_reason_set);
        pop(r13);
        pop(r14);
        pop(rdi);
        pop(rsi);
        lock();
        or_(dword[r15 + jsi.offsetof_halt_reason], halt_reason_on_run);
 
        if (isRet) {
            pop(rbx);
            pop(ABI_RETURN);
            jmp(ret_code_entry, T_NEAR);
        }
    }
}
void BlockOfCode::GenHaltReasonSet(Xbyak::Label& run_code_entry) {
    Xbyak::Label _dummy;
    GenHaltReasonSetImpl(false, run_code_entry, _dummy);
}
void BlockOfCode::GenHaltReasonSet(Xbyak::Label& run_code_entry, Xbyak::Label& ret_code_entry) {
    GenHaltReasonSetImpl(true, run_code_entry, ret_code_entry);
}
void BlockOfCode::GenHaltReasonSetImpl(bool isRet, Xbyak::Label& run_code_entry, Xbyak::Label& ret_code_entry) {
    Xbyak::Label normal_code, halt_reason_set;
    if (halt_reason_on_run) {
        if (isRet) {
            push(ABI_RETURN);
            push(rbx);
            mov(rbx, ABI_RETURN);
        }
        push(rsi);
        push(rdi);
        push(r14);
        push(r13);
 
        mov(esi, word[rbx - 4]);
        mov(r14, dword[rbx - 16]);
 
        mov(r13, trace_scope_begin);
        cmp(r14, r13);
        jl(normal_code, T_NEAR);
        mov(r13, trace_scope_end);
        cmp(r14, r13);
        jge(normal_code, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfc000000);
        cmp(edi, 0x94000000);//BL
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd63f0000);//BLR
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffff800);
        cmp(edi, 0xd63f0800);//BLRxxx
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xff000010);
        cmp(edi, 0x54000000);//B.cond
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xff000010);
        cmp(edi, 0x54000010);//BC.cond
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x35000000);//CBNZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x34000000);//CBZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x37000000);//TBNZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0x7f000000);
        cmp(edi, 0x36000000);//TBZ
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfc000000);
        cmp(edi, 0x14000000);//B
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd61f0000);//BR
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffff800);
        cmp(edi, 0xd61f0800);//BRxxx
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffc1f);
        cmp(edi, 0xd65f0000);//RET
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffbff);
        cmp(edi, 0xd65f0bff);//RETAA, RETAB
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xffc0001f);
        cmp(edi, 0x5500001f);//RETAASPPC, RETABSPPC
        jz(halt_reason_set, T_NEAR);
 
        mov(edi, esi);
        and_(edi, 0xfffffbe0);
        cmp(edi, 0xd65f0be0);//RETAASPPC, RETABSPPC
        jz(halt_reason_set, T_NEAR);
 
        L(normal_code);
        pop(r13);
        pop(r14);
        pop(rdi);
        pop(rsi);
        if (isRet) {
            pop(rbx);
            pop(ABI_RETURN);
        }
        jmp(run_code_entry, T_NEAR);
 
        L(halt_reason_set);
        pop(r13);
        pop(r14);
        pop(rdi);
        pop(rsi);
        lock();
        or_(dword[r15 + jsi.offsetof_halt_reason], halt_reason_on_run);
 
        if (isRet) {
            pop(rbx);
            pop(ABI_RETURN);
            jmp(ret_code_entry, T_NEAR);
        }
    }
}
align();
run_code = getCurr<RunCodeFuncType>();
 
// This serves two purposes:
// 1. It saves all the registers we as a callee need to save.
// 2. It aligns the stack so that the code the JIT emits can assume
//    that the stack is appropriately aligned for CALLs.
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
mov(rbx, ABI_PARAM2);  // save temporarily in non-volatile register
 
if (cb.enable_cycle_counting) {
    cb.GetTicksRemaining->EmitCall(*this);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
 
GenHaltReasonSet(run_code_entry);
 
L(run_code_entry);
SwitchMxcsrOnEntry();
jmp(rbx);
align();
run_code = getCurr<RunCodeFuncType>();
 
// This serves two purposes:
// 1. It saves all the registers we as a callee need to save.
// 2. It aligns the stack so that the code the JIT emits can assume
//    that the stack is appropriately aligned for CALLs.
ABI_PushCalleeSaveRegistersAndAdjustStack(*this, sizeof(StackLayout));
 
mov(r15, ABI_PARAM1);
mov(rbx, ABI_PARAM2);  // save temporarily in non-volatile register
 
if (cb.enable_cycle_counting) {
    cb.GetTicksRemaining->EmitCall(*this);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
    mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
}
 
rcp(*this);
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
 
GenHaltReasonSet(run_code_entry);
 
L(run_code_entry);
SwitchMxcsrOnEntry();
jmp(rbx);
align();
return_from_run_code[0] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller, T_NEAR);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller, T_NEAR);
}
cb.LookupBlock->EmitCall(*this);
 
Xbyak::Label next_code_entry0;
GenHaltReasonSet(next_code_entry0, return_to_caller);
L(next_code_entry0);
jmp(ABI_RETURN);
align();
return_from_run_code[0] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller, T_NEAR);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller, T_NEAR);
}
cb.LookupBlock->EmitCall(*this);
 
Xbyak::Label next_code_entry0;
GenHaltReasonSet(next_code_entry0, return_to_caller);
L(next_code_entry0);
jmp(ABI_RETURN);
align();
return_from_run_code[MXCSR_ALREADY_EXITED] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller_mxcsr_already_exited, T_NEAR);
}
SwitchMxcsrOnEntry();
cb.LookupBlock->EmitCall(*this);
 
Xbyak::Label next_code_entry1;
GenHaltReasonSet(next_code_entry1, return_to_caller_mxcsr_already_exited);
L(next_code_entry1);
jmp(ABI_RETURN);
align();
return_from_run_code[MXCSR_ALREADY_EXITED] = getCurr<const void*>();
 
cmp(dword[r15 + jsi.offsetof_halt_reason], 0);
jne(return_to_caller_mxcsr_already_exited, T_NEAR);
if (cb.enable_cycle_counting) {
    cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
    jng(return_to_caller_mxcsr_already_exited, T_NEAR);
}
SwitchMxcsrOnEntry();
cb.LookupBlock->EmitCall(*this);
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-6-3 11:56 被dreaman编辑 ,原因: 更新链接
收藏
免费 11
支持
分享
最新回复 (17)
雪    币: 10960
活跃值: (2920)
能力值: ( LV5,RANK:71 )
在线值:
发帖
回帖
粉丝
2
mark一下
2024-3-14 16:56
0
雪    币: 3070
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-3-15 13:43
1
雪    币: 4120
活跃值: (5822)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
4
厉害
2024-3-15 22:24
0
雪    币: 1392
活跃值: (5172)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
5
arm 2 x86 做得比较好的似乎是 houdini? 这个开源工程可以学习学习
2024-3-16 10:05
0
雪    币: 1392
活跃值: (5172)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
6
奇怪的是任天堂为啥硬刚switch模拟器。gamecube/wii的dolphin的就不管了呢
2024-3-16 10:27
0
雪    币: 1316
活跃值: (512)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
7
IamHuskar 奇怪的是任天堂为啥硬刚switch模拟器。gamecube/wii的dolphin的就不管了呢
我觉得是因为steam推出的游戏主机,在宣传时贴上了yuzu的图标,任天堂可不管在PC上跑他们游戏,但现在有一个游戏机也要能跑他们游戏了,应该对他们是真正的威胁了
2024-3-16 11:38
0
雪    币: 1316
活跃值: (512)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
8
IamHuskar arm 2 x86 做得比较好的似乎是 houdini? 这个开源工程可以学习学习
intel houdini代码开源地址是啥啊
2024-3-16 11:43
0
雪    币: 2092
活跃值: (155)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
这个玩的比较深了????单机游戏,娱乐为主,谢谢楼主分享经验
2024-3-16 12:49
0
雪    币: 1392
活跃值: (5172)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
10
dreaman intel houdini代码开源地址是啥啊
houdini不开源。主要是之前为了 x86上跑arm游戏 intel上海做的。
2024-3-16 15:07
0
雪    币: 1316
活跃值: (512)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
11
IamHuskar houdini不开源。主要是之前为了 x86上跑arm游戏 intel上海做的。
明白了
2024-3-16 17:24
0
雪    币: 12453
活跃值: (9422)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
大佬牛逼。。。
得亏出新闻的时候,clone了一份,还想着啥时候看看了。
2024-3-16 21:22
0
雪    币: 2458
活跃值: (4586)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
13
处于好奇,想问下为什么不再qemu-tcg的基础上改,而是自己写jit呢
2024-3-19 17:16
0
雪    币: 1316
活跃值: (512)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
14
小白养的菜鸡 处于好奇,想问下为什么不再qemu-tcg的基础上改,而是自己写jit呢
我也不太清楚真正原因。我猜可能是为了更可控吧,比如方便实现单步调试与内存调试功能。
2024-3-19 20:49
0
雪    币: 2458
活跃值: (4586)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
15
dreaman 我也不太清楚真正原因。我猜可能是为了更可控吧,比如方便实现单步调试与内存调试功能。
嗯嗯
2024-3-20 09:41
0
雪    币: 1392
活跃值: (5172)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
16
小白养的菜鸡 处于好奇,想问下为什么不再qemu-tcg的基础上改,而是自己写jit呢
qemu的tcg是通用的。通用性好,性能来说相对不行。所以模拟器都是自己写jit的。
2024-3-22 12:21
0
雪    币: 2458
活跃值: (4586)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
17
IamHuskar qemu的tcg是通用的。通用性好,性能来说相对不行。所以模拟器都是自己写jit的。
嗷嗷,了解了
2024-3-23 22:25
0
雪    币: 234
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
学习一下,我现在只修改汇编代码 04000000,可以尝试基于内存的方法,谢谢。
2024-11-10 15:55
0
游客
登录 | 注册 方可回帖
返回
//