第7章内容添加

This commit is contained in:
dqzg12300 2023-03-24 00:45:29 +08:00
parent 8833138a1c
commit 7c76e5cc75

View File

@ -947,11 +947,502 @@ void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa,
}
```
到这时就调用到了`ArtMethod``Invoke`函数,这里将`soa`、参数的数组指针,参数数组大小,返回值指针,调用函数的描述符号传递了过去。在开始进入关键函数前,先对返回值指针`JValue* result`进行简单介绍。
到这时就调用到了`ArtMethod``Invoke`函数,这里将参数的数组指针,参数数组大小,返回值指针,调用函数的描述符号传递了过去。在开始进入关键函数前,先对返回值指针`JValue* result`进行简单介绍。
`JValue`是用于存储和传递`Java`方法返回值的联合体。包含了各种基本类型和引用类型的成员变量。下面是该联合体的定义。
```c++
union PACKED(alignof(mirror::Object*)) JValue {
// We default initialize JValue instances to all-zeros.
JValue() : j(0) {}
template<typename T> ALWAYS_INLINE static JValue FromPrimitive(T v);
int8_t GetB() const { return b; }
void SetB(int8_t new_b) {
j = ((static_cast<int64_t>(new_b) << 56) >> 56); // Sign-extend to 64 bits.
}
uint16_t GetC() const { return c; }
void SetC(uint16_t new_c) {
j = static_cast<int64_t>(new_c); // Zero-extend to 64 bits.
}
double GetD() const { return d; }
void SetD(double new_d) { d = new_d; }
float GetF() const { return f; }
void SetF(float new_f) { f = new_f; }
int32_t GetI() const { return i; }
void SetI(int32_t new_i) {
j = ((static_cast<int64_t>(new_i) << 32) >> 32); // Sign-extend to 64 bits.
}
int64_t GetJ() const { return j; }
void SetJ(int64_t new_j) { j = new_j; }
mirror::Object* GetL() const REQUIRES_SHARED(Locks::mutator_lock_) {
return l;
}
ALWAYS_INLINE
void SetL(ObjPtr<mirror::Object> new_l) REQUIRES_SHARED(Locks::mutator_lock_);
int16_t GetS() const { return s; }
void SetS(int16_t new_s) {
j = ((static_cast<int64_t>(new_s) << 48) >> 48); // Sign-extend to 64 bits.
}
uint8_t GetZ() const { return z; }
void SetZ(uint8_t new_z) {
j = static_cast<int64_t>(new_z); // Zero-extend to 64 bits.
}
mirror::Object** GetGCRoot() { return &l; }
private:
uint8_t z;
int8_t b;
uint16_t c;
int16_t s;
int32_t i;
int64_t j;
float f;
double d;
mirror::Object* l;
};
```
`JValue`结构体的大小为8个字节对齐结构体提供了一些成员函数例如`GetXXX``SetXXX`等函数,用于获取和设置不同类型的返回值。`alignof(mirror::Object*)`的具体值取决于编译器和操作系统的不同一般为4或8。
对参数以及返回值的在`C++`中的表示有了初步的了解后,开始继续查看函数调用过程中的关键函数`ArtMethod::Invoke`,下面是具体实现代码。
```c++
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
...
// 将当前的环境(也就是函数调用时的程序计数器、堆栈指针等信息)保存到一个栈帧中。这个栈帧通常会被分配在堆上,并且由垃圾回收器来管理。在函数返回时,这个栈帧会被弹出,恢复之前的环境。
ManagedStack fragment;
self->PushManagedStackFragment(&fragment);
Runtime* runtime = Runtime::Current();
// IsForceInterpreter为true表示强制使用解释器执行函数
// 这里的条件是如果设置了强制走解释器执行并且非native函数并且非代理函数并且可执行的函数则符合条件
if (UNLIKELY(!runtime->IsStarted() ||
(self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) {
if (IsStatic()) {
// 静态函数调用
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args, result, /*stay_in_interpreter=*/ true);
} else {
// 非静态函数调用
mirror::Object* receiver =
reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(
self, this, receiver, args + 1, result, /*stay_in_interpreter=*/ true);
}
} else {
...
// 是否有已编译的快速执行代码的入口点
bool have_quick_code = GetEntryPointFromQuickCompiledCode() != nullptr;
if (LIKELY(have_quick_code)) {
...
// 走快速调用方式,比解释器执行的性能高。
if (!IsStatic()) {
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
} else {
(*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
}
...
} else {
LOG(INFO) << "Not invoking '" << PrettyMethod() << "' code=null";
if (result != nullptr) {
result->SetJ(0);
}
}
}
// 从栈帧中还原当前环境
self->PopManagedStackFragment(fragment);
}
```
根据以上代码得到的结论是,函数执行的路线有两条,`EnterInterpreterFromInvoke`由解释器执行和`art_quick_invoke_stub`快速执行通道。
`art_quick_invoke_stub`是由一段汇编完成对函数的执行,该函数充分利用寄存器并尽可能地减少堆栈访问次数,以提高`Java`方法的执行效率。,虽然快速执行通道的效率会更加高,但是可读性差,但是对于学习执行过程和修改执行流程来说,解释器执行会更加简单易改。所以接下来跟进解释器执行来了解执行的细节。继续跟踪`EnterInterpreterFromInvoke`函数。
```c++
void EnterInterpreterFromInvoke(Thread* self,
ArtMethod* method,
ObjPtr<mirror::Object> receiver,
uint32_t* args,
JValue* result,
bool stay_in_interpreter) {
...
// 获取函数中的指令信息
CodeItemDataAccessor accessor(method->DexInstructionData());
uint16_t num_regs;
uint16_t num_ins;
if (accessor.HasCodeItem()) {
// 获取寄存器的数量和参数的数量
num_regs = accessor.RegistersSize();
num_ins = accessor.InsSize();
} else if (!method->IsInvokable()) {
self->EndAssertNoThreadSuspension(old_cause);
method->ThrowInvocationTimeError();
return;
} else {
DCHECK(method->IsNative()) << method->PrettyMethod();
// 从函数描述符中计算出寄存器数量和参数数量
// 这里将num_regs和num_ins都赋值的原因是方法的前几个参数通常会存储在寄存器中而不是堆栈中。因此num_regs和num_ins的值应该是相同的都代表了当前方法使用的寄存器数量也就是用于存储参数和局部变量等数据的寄存器数量。
num_regs = num_ins = ArtMethod::NumArgRegisters(method->GetShorty());
// 非静态函数的情况会多一个this参数所以寄存器数量和参数数量+1
if (!method->IsStatic()) {
num_regs++;
num_ins++;
}
}
// 创建一个新的ShadowFrame作为当前栈将当前环境保存在其中并且推入栈帧供当前线程调用方法时使用
ShadowFrame* last_shadow_frame = self->GetManagedStack()->GetTopShadowFrame();
ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
CREATE_SHADOW_FRAME(num_regs, last_shadow_frame, method, /* dex pc */ 0);
ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
self->PushShadowFrame(shadow_frame);
// 计算出将要使用的第一个寄存器
size_t cur_reg = num_regs - num_ins;
// 非静态函数的情况第一个寄存器的值为this所以设置其为引用类型
if (!method->IsStatic()) {
// receiver变量表示方法调用的第一个参数
CHECK(receiver != nullptr);
shadow_frame->SetVRegReference(cur_reg, receiver);
++cur_reg;
}
uint32_t shorty_len = 0;
const char* shorty = method->GetShorty(&shorty_len);
// 遍历所有参数
for (size_t shorty_pos = 0, arg_pos = 0; cur_reg < num_regs; ++shorty_pos, ++arg_pos, cur_reg++) {
DCHECK_LT(shorty_pos + 1, shorty_len);
switch (shorty[shorty_pos + 1]) {
//L 表示这个参数是个引用类型比如Ljava/lang/String;
case 'L': {
ObjPtr<mirror::Object> o =
reinterpret_cast<StackReference<mirror::Object>*>(&args[arg_pos])->AsMirrorPtr();
// 将转换好的数据设置到当前栈中
shadow_frame->SetVRegReference(cur_reg, o);
break;
}
case 'J': case 'D': {
// J或者D的数据类型要占用两个寄存器存放。
uint64_t wide_value = (static_cast<uint64_t>(args[arg_pos + 1]) << 32) | args[arg_pos];
// 合并后的数据设置到栈中
shadow_frame->SetVRegLong(cur_reg, wide_value);
cur_reg++;
arg_pos++;
break;
}
default:
// 普通的整型数据设置到栈中
shadow_frame->SetVReg(cur_reg, args[arg_pos]);
break;
}
}
self->EndAssertNoThreadSuspension(old_cause);
// 静态函数的情况需要检查所在的类是否已经正常初始化。
if (method->IsStatic()) {
ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
if (UNLIKELY(!declaring_class->IsVisiblyInitialized())) {
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(declaring_class));
if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
self, h_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true))) {
CHECK(self->IsExceptionPending());
self->PopShadowFrame();
return;
}
DCHECK(h_class->IsInitializing());
}
}
// 非native函数执行
if (LIKELY(!method->IsNative())) {
// 解释执行的关键函数
JValue r = Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);
if (result != nullptr) {
*result = r;
}
} else {
// native函数的解释执行
args = shadow_frame->GetVRegArgs(method->IsStatic() ? 0 : 1);
if (!Runtime::Current()->IsStarted()) {
UnstartedRuntime::Jni(self, method, receiver.Ptr(), args, result);
} else {
InterpreterJni(self, method, shorty, receiver, args, result);
}
}
// 弹出栈帧,还原到执行后的栈环境
self->PopShadowFrame();
}
```
在这个函数中,为即将执行的函数准备好了栈帧环境,将参数填入了`shadow_frame`栈帧中。并且获取出了函数要执行的指令信息`accessor`。最后通过`Execute`执行该函数。
```java
static inline JValue Execute(
Thread* self,
const CodeItemDataAccessor& accessor,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false,
bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {
...
// 是否需要从解释器模式切换到编译模式。
if (LIKELY(!from_deoptimize)) {
...
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
// 从当前线程栈帧中获取要执行的函数
ArtMethod *method = shadow_frame.GetMethod();
// 是否有注册Method Entry 监听器
if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
// 触发 Method Entry 监听器,并传递相应的参数
instrumentation->MethodEnterEvent(self,
shadow_frame.GetThisObject(accessor.InsSize()),
method,
0);
...
// 是否有未处理的异常
if (UNLIKELY(self->IsExceptionPending())) {
...
return ret;
}
}
// stay_in_interpreter 表示是否需要停留在解释器模式self->IsForceInterpreter() 表示是否强制使用解释器模式。所以内部是不走解释器执行的处理,走编译模式执行
if (!stay_in_interpreter && !self->IsForceInterpreter()) {
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
// 判断当前方法是否可以使用 JIT 编译后的机器码执行
jit->MethodEntered(self, shadow_frame.GetMethod());
if (jit->CanInvokeCompiledCode(method)) {
JValue result;
// 直接栈帧推出
self->PopShadowFrame();
uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();
// 调用该函数的机器码实现
ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);
// 重新推入栈帧
self->PushShadowFrame(&shadow_frame);
return result;
}
}
}
}
// 从栈帧中获取要执行的当前函数
ArtMethod* method = shadow_frame.GetMethod();
...
// kSwitchImplKind表示当前实现是否使用基于 switch 语句的解释器实现。
if (kInterpreterImplKind == kSwitchImplKind ||
UNLIKELY(!Runtime::Current()->IsStarted()) ||
!method->IsCompilable() ||
method->MustCountLocks() ||
Runtime::Current()->IsActiveTransaction()) {
// 使用switch解释器执行
return ExecuteSwitch(
self, accessor, shadow_frame, result_register, /*interpret_one_instruction=*/ false);
}
CHECK_EQ(kInterpreterImplKind, kMterpImplKind);
// 编译执行函数
while (true) {
// 是否支持Mterp解释器执行
if (!self->UseMterp()) {
return ExecuteSwitch(
self, accessor, shadow_frame, result_register, /*interpret_one_instruction=*/ false);
}
// 执行目标函数
bool returned = ExecuteMterpImpl(self,
accessor.Insns(),
&shadow_frame,
&result_register);
if (returned) {
return result_register;
} else {
// 失败的情况继续采用switch解释器执行
result_register = ExecuteSwitch(
self, accessor, shadow_frame, result_register, /*interpret_one_instruction=*/ true);
if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {
return result_register;
}
}
}
}
```
简单看完该函数后,在继续深入前,先将其中的几个知识点进行介绍。
编译模式`Compiled Mode`是一种执行方式,它将应用程序代码编译成机器码后再执行。相较于解释器模式,编译模式具有更高的执行效率和更好的性能表现。
` Android `应用程序中,编译模式采用的是 `Just-In-TimeJIT`编译技术。当一个方法被多次调用时,系统会自动将其编译成本地机器码,并缓存起来以备下次使用。
当一个方法被编译成本地机器码后,其执行速度将显著提高。因为与解释器模式相比,编译模式不需要逐条解释代码,而是直接执行编译好的机器码。
由于编译过程需要一定的时间,因此在程序启动或者第一次运行新方法时,可能会出现一些额外的延迟。所以,在实际应用中,系统通常会采用一些策略,如预热机制等,来优化编译模式的性能表现。编译模式是一种性能更高、效率更好的执行方式,可以帮助应用程序在运行时获得更好的响应速度和用户体验。
`Method Entry` 监听器是` Android `系统中的一种监听器,它可以用来监听应用程序的方法入口。当一个方法被调用时,系统会触发` Method Entry `监听器,并将当前线程、当前方法和调用栈信息等相关数据传递给监听器。
`Android Studio `在调试模式下会自动为每个线程启动一个监听器,并在方法进入和退出时触发相应的事件。这些事件包括 `Method Entry`(方法入口)、`Method Exit`(方法出口)等。
`ExecuteSwitch`是基于 switch 语句实现的一种解释器,用于执行当前方法的指令集。在 `Android` 应用程序中,每个方法都会对应一组指令集,用于描述该方法的具体实现。当该方法被调用时,系统需要按照指令集来执行相应的操作,从而实现该方法的功能并计算出结果。
`ExecuteMterpImpl`是基于` MterpMethod Interpreter`技术实现。`Mterp `技术使用指令集解释器来执行应用程序的代码,相比于` JIT `编译模式可以更快地启动和执行短小精悍的方法,同时也可以避免 `JIT `编译带来的额外开销。
`Mterp `模式下,`Dex `指令集被转化成了一组` C++ `的函数,这些函数对应` Dex `指令集中的每一条指令。`ExecuteMterpImpl`实际上就是调用这些函数来逐条解释执行当前方法的指令集。
` Android 4.4`中,系统首次引入了 `Mterp` 技术来加速应用程序的解释执行。在此之后的 `Android`版本中,`Mterp `技术得到了不断优化和完善,并逐渐成为` Android `平台的主要方法执行方式之一。
` Android 6.0`开始,`Dalvik `运行时环境被弃用,取而代之的是` ART `运行时环境。`ART` 运行时环境可以通过` JIT `编译、`AOT `编译和` Mterp `等多种方式来执行应用程序的代码,其中` Mterp `技术被广泛使用于 `Android` 应用程序的解释执行过程中。但是对于某些特定的场景和应用程序,系统可能还是会选择其他的执行方式来获得更好的性能和效率。
`ExecuteMterpImpl`使用了汇编语言和` C++`语言混合编写,需要有一定的汇编和` C++ `编程经验才能理解其含义和功能。该代码主要实现了以下功能:
1.保存当前方法的返回值寄存器和指令集
2.设置方法执行的环境和参数,包括` vregs `数组、`dex_pc `寄存器等
3.为当前方法设置热度倒计时,并根据热度值来判断是否需要启用` Mterp `技术
4.执行当前方法的指令集,逐条解释执行 `Dex `指令
下面看`ExecuteMterpImpl`实现代码。
```assembly
// 从xPC寄存器中获取一条指令
.macro FETCH_INST
ldrh wINST, [xPC]
.endm
// 从指令中获取操作码指令最顶部2个字节就是操作码所以这里拿操作码是 & 0xff的意思
.macro GET_INST_OPCODE reg
and \reg, xINST, #255
.endm
// 跳转到操作码处理逻辑
.macro GOTO_OPCODE reg
add \reg, xIBASE, \reg, lsl #${handler_size_bits}
br \reg
.endm
ENTRY ExecuteMterpImpl
.cfi_startproc
// 保存寄存器信息
SAVE_TWO_REGS_INCREASE_FRAME xPROFILE, x27, 80
SAVE_TWO_REGS xIBASE, xREFS, 16
SAVE_TWO_REGS xSELF, xINST, 32
SAVE_TWO_REGS xPC, xFP, 48
SAVE_TWO_REGS fp, lr, 64
// fp寄存器指向栈顶
add fp, sp, #64
/* 记录对应返回值的寄存器 */
str x3, [x2, #SHADOWFRAME_RESULT_REGISTER_OFFSET]
/* 记录dex文件中的指令的指针 */
str x1, [x2, #SHADOWFRAME_DEX_INSTRUCTIONS_OFFSET]
mov xSELF, x0
ldr w0, [x2, #SHADOWFRAME_NUMBER_OF_VREGS_OFFSET]
add xFP, x2, #SHADOWFRAME_VREGS_OFFSET // 计算局部变量表的偏移地址
add xREFS, xFP, w0, uxtw #2 // 计算局部变量引用表的偏移地址
ldr w0, [x2, #SHADOWFRAME_DEX_PC_OFFSET] // 获取当前Dex中的PC
add xPC, x1, w0, uxtw #1 // 将Dex PC转换为地址并保存到寄存器xPC中
CFI_DEFINE_DEX_PC_WITH_OFFSET(CFI_TMP, CFI_DEX, 0)
EXPORT_PC // 将Dex PC导出
/* Starting ibase */
ldr xIBASE, [xSELF, #THREAD_CURRENT_IBASE_OFFSET]
/* Set up for backwards branches & osr profiling */
ldr x0, [xFP, #OFF_FP_METHOD] // 获取当前方法的方法指针
add x1, xFP, #OFF_FP_SHADOWFRAME // 计算拿到当前线程的栈帧
mov x2, xSELF // 将当前线程对象保存到寄存器x2中
bl MterpSetUpHotnessCountdown // 热度计数器调整
mov wPROFILE, w0 // 将热度计数器的宽度赋值给寄存器wPROFILE
/* start executing the instruction at rPC */
FETCH_INST // 获取下一条指令
GET_INST_OPCODE ip // 从指令中获取操作码
GOTO_OPCODE ip // 跳转到操作码处理逻辑
/* NOTE: no fallthrough */
// cfi info continues, and covers the whole mterp implementation.
END ExecuteMterpImpl
```
这些操作码可以通过`Opcodes`找到其对应的对应,代码如下。
```java
public interface Opcodes {
...
int OP_INVOKE_STATIC = 0x0071;
int OP_INVOKE_INTERFACE = 0x0072;
int OP_INVOKE_VIRTUAL_RANGE = 0x0074;
int OP_INVOKE_SUPER_RANGE = 0x0075;
int OP_INVOKE_DIRECT_RANGE = 0x0076;
int OP_INVOKE_STATIC_RANGE = 0x0077;
int OP_INVOKE_INTERFACE_RANGE = 0x0078;
...
}
```
而在`invoke.S`汇编文件中,会有其对应操作码的具体实现。
```assembly
%def op_invoke_static():
% invoke(helper="MterpInvokeStatic")
%def op_invoke_static_range():
% invoke(helper="MterpInvokeStaticRange")
%def op_invoke_super():
% invoke(helper="MterpInvokeSuper")
/*
* Handle a "super" method call.
*
* for: invoke-super, invoke-super/range
*/
/* op vB, {vD, vE, vF, vG, vA}, class@CCCC */
/* op vAA, {vCCCC..v(CCCC+AA-1)}, meth@BBBB */
%def op_invoke_super_range():
% invoke(helper="MterpInvokeSuperRange")
%def op_invoke_virtual():
% invoke(helper="MterpInvokeVirtual")
/*
* Handle a virtual method call.
*
* for: invoke-virtual, invoke-virtual/range
*/
/* op vB, {vD, vE, vF, vG, vA}, class@CCCC */
/* op vAA, {vCCCC..v(CCCC+AA-1)}, meth@BBBB */
%def op_invoke_virtual_range():
% invoke(helper="MterpInvokeVirtualRange")
```
到这里,就找到对应的执行`C++`函数将`Dex`的指令逐一进行执行处理。`Mterp`的执行流程到这里就非常清晰了。下一步开始分析`switch`解释器的执行流程。
`JValue`是被广泛用于`Java`方法的调用过程中的结构体,用于存储和传递`Java`方法的返回值。`JValue`结构体包含了`32`位和`64`位两种数据类型的变量,分别用于表示`Java`方法返回值的基本类型和引用类型。
### 7.3.4 动态加载壳的实现