吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4288|回复: 13
收起左侧

[Android CrackMe] Android Linker详解

  [复制链接]
roysue 发表于 2021-10-15 14:03
CM是什么?Crackme是什么?这是什么东西?楼主发的什么?
他们都是一些公开给别人尝试破解的小程序,制作 Crackme 的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位 Cracker,想挑战一下其它 Cracker 的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破解,KeyGenMe是要求别人做出它的 keygen (序号产生器), ReverseMe 要求别人把它的算法做出逆向分析, UnpackMe 是要求别人把它成功脱壳,本版块禁止回复非技术无关水贴。

本帖最后由 roysue 于 2021-10-15 14:08 编辑

Android Linker详解

本文目的

Unidbg在对So进行模拟执行的时候,需要先将So文件加载到内存,配置So的进程映像,然后使用CPU模拟器(Unicorn、Dynamic等)对So进行模拟执行。本文的目的是为了彻底搞懂So文件是如何加载到内存的,以及加载进内存之后做了什么,史无巨细,握住方向盘

Linker入口

我们在Android程序中,往往会使用到JNI编程来加快某些算法的运行或增加APP的逆向难度。当然这不是Android的新特性,它是Java自带的本地编程接口,可以使我们的Java程序能够调用本地语言。

当我们在Android程序想使用本地编译的So库,第一步就是要将So加载进来对吧,Android Studio创建C/C++ Native模板的时候,它会在我们的MainActivity类中加这么一段代码

static{
    System.loadLibrary("native-lib");
}

这句代码的作用就是将So加载进来供Android程序来使用,所以以此为入口,开始分析

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/System.java#525

public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

又调用了Runtime类的loadLibrary,第二个参数为调用类的ClassLoader

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/Runtime.java#354

void loadLibrary(String libraryName, ClassLoader loader) {
    if (loader != null) {
        //调用findLibrary通过名称("native-lib")寻找真实库文件名
        String filename = loader.findLibrary(libraryName);
        if (filename == null) {
            throw new UnsatisfiedLinkError("...");
        }
        //如果找到了,进行加载
        String error = doLoad(filename, loader);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
        return;
    }

    String filename = System.mapLibraryName(libraryName);
    List<String> candidates = new ArrayList<String>();
    String lastError = null;
    for (String directory : mLibPaths) {
        String candidate = directory + filename;
        candidates.add(candidate);

        if (IoUtils.canOpenReadOnly(candidate)) {
            //这里是还有其他的路径来搜索我们的库文件名,都是调用doLoad方法
            String error = doLoad(candidate, loader);
            if (error == null) {
                return; // We successfully loaded the library. Job done.
            }
            lastError = error;
        }
    }

    if (lastError != null) {
        throw new UnsatisfiedLinkError(lastError);
    }
    throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

接着看doLoad方法

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/Runtime.java#393

private String doLoad(String name, ClassLoader loader) {
    String ldLibraryPath = null;
    if (loader != null && loader instanceof BaseDexClassLoader) {
        //如果是BaseDexClassLoader,获取系统so的路径
        ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
    }
    synchronized (this) {
        //继续调用nativeLoad,还加了同步锁
        return nativeLoad(name, loader, ldLibraryPath);
    }
}

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/Runtime.java#426

private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);

继续往下分析,找到nativeLoad对应的C层函数

http://androidxref.com/4.4.4_r1/xref/art/runtime/native/java_lang_Runtime.cc#43

```c++
static jstring Runtime_nativeLoad(JNIEnv env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) {
//...各种检查
mirror::ClassLoader
classLoader = soa.Decode<mirror::ClassLoader>(javaLoader);
std::string detail;
JavaVMExt
vm = Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, detail);
if (success) {
return NULL;
}

// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(detail.c_str());
}

最关键的函数是vm->LoadNativeLibrary,继续往下跟

>http://androidxref.com/4.4.4_r1/xref/art/runtime/jni_internal.cc#3120

```c++
bool JavaVMExt::LoadNativeLibrary(const std::string& path, ClassLoader* class_loader,
                                  std::string& detail) {
    //...
    self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);
    //调用dlopen加载So,并返回一个handle句柄
    void* handle = dlopen(path.empty() ? NULL : path.c_str(), RTLD_LAZY);
    self->TransitionFromSuspendedToRunnable();

    VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";
    //...
    //此时如果So加载正常,会调用dlsym查找JNI_OnLoad符号,并执行
    void* sym = dlsym(handle, "JNI_OnLoad");
    if (sym == NULL) {
        VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
        was_successful = true;
    } else {
    }
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    ClassLoader* old_class_loader = self->GetClassLoaderOverride();
    self->SetClassLoaderOverride(class_loader);
    int version = 0;
    {
        ScopedThreadStateChange tsc(self, kNative);
        VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
        //在这里调用JNIOnload
        version = (*jni_on_load)(this, NULL);
    }
    //...
    library->SetResult(was_successful);
    return was_successful;
}

我们分析上面的函数知道,此函数主要做了两件事

  • 调用dlopen加载So
  • 查找So中的JNI_OnLoad函数,并执行
    继续往下分析dlopen

http://androidxref.com/4.4.4_r1/xref/bionic/linker/dlfcn.cpp#63

```c++
void dlopen(const char filename, int flags) {
ScopedPthreadMutexLocker locker(&gDlMutex);
soinfo* result = do_dlopen(filename, flags);
if (result == NULL) {
__bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
return NULL;
}
return result;
}

调用了do_dlopen

## So的装载

>http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#823

```c++
soinfo* do_dlopen(const char* name, int flags) {
  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
    DL_ERR("invalid flags to dlopen: %x", flags);
    return NULL;
  }
  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
  soinfo* si = find_library(name);
  if (si != NULL) {
    si->CallConstructors();
  }
  set_soinfo_pool_protection(PROT_READ);
  return si;
}

分析到这里,终于进入Linker部分了,上面的篇幅我们由System.loadLibrary()方法,找到了Linker的do_dlopen函数,这个函数就可以说是Linker开始加载的地方了。这个函数主要做了两件事

  • 调用函数find_library,返回soinfo。soinfo就是so被加载到内存的一个代表,存放了内存中so的信息
  • 调用soinfo的CallConstructors函数,做了一些初始化操作(Iint、init.array)

继续分析find_library

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#785

```c++
static soinfo find_library(const char name) {
soinfo* si = find_library_internal(name);
if (si != NULL) {
si->ref_count++;
}
return si;
}

这个函数的作用很简单
- 调用find_library_internal
- so的引用计数+1

继续分析find_library_internal函数
>http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#751

```c++
static soinfo* find_library_internal(const char* name) {
  if (name == NULL) {
    return somain;
  }
  //寻找已经加载过的so,当我们的so被加载完成后,会放到已加载列表,再次调用System.loadLibrary的时候不需要进行二次加载
  soinfo* si = find_loaded_library(name);
  if (si != NULL) {
    if (si->flags & FLAG_LINKED) {
      return si;
    }
    DL_ERR("OOPS: recursive link to \"%s\"", si->name);
    return NULL;
  }

  TRACE("[ '%s' has not been loaded yet.  Locating...]", name);
  //如果没有被加载过,就调用load_library进行加载
  si = load_library(name);
  if (si == NULL) {
    return NULL;
  }

  // At this point we know that whatever is loaded @ base is a valid ELF
  // shared library whose segments are properly mapped in.
  TRACE("[ init_library base=0x%08x sz=0x%08x name='%s' ]",
        si->base, si->size, si->name);
  // so被加载后,进行链接
  if (!soinfo_link_image(si)) {
    munmap(reinterpret_cast<void*>(si->base), si->size);
    soinfo_free(si);
    return NULL;
  }

  return si;
}

这个函数主要做了3个事情:

  • 判断想要加载的so是否已经被加载过
  • 如果没有被加载过,调用load_library进行加载
  • 加载完成后,调用soinfo_link_image函数进行链接
    也就体现了我们So装载的主要两个步骤
  • So的装载
  • So的链接
    在上面我们还有一个调用soinfo的CallConstructors函数,这个也可以作为第三个
  • So的初始化

那么我们假设我们的So是第一次进行加载,继续分析load_library函数,看看linker如何装载我们的So

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#702

```c++
static soinfo load_library(const char name) {
// 打开So文件,拿到文件描述符fd
int fd = open_library(name);
if (fd == -1) {
DL_ERR("library \"%s\" not found", name);
return NULL;
}

//创建ElfReader对象,并调用Load方法
ElfReader elf_reader(name, fd);
if (!elf_reader.Load()) {
    return NULL;
}
//生成soinfo,并根据elf_reader的结果进行赋值
const char* bname = strrchr(name, '/');
soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
if (si == NULL) {
    return NULL;
}
si->base = elf_reader.load_start();
si->size = elf_reader.load_size();
si->load_bias = elf_reader.load_bias();
si->flags = 0;
si->entry = 0;
si->dynamic = NULL;
si->phnum = elf_reader.phdr_count();
si->phdr = elf_reader.loaded_phdr();
return si;

}

那么我们主要来分析elf_reader.Load()函数

>http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker_phdr.cpp#134

```c++
bool ElfReader::Load() {
  return ReadElfHeader() &&
         VerifyElfHeader() &&
         ReadProgramHeader() &&
         ReserveAddressSpace() &&
         LoadSegments() &&
         FindPhdr();
}

Load函数分别调用了6个函数

  • ReadElfHeader 读取ElfHeader
  • VerifyElfHeader 验证ElfHeader
  • ReadProgramHeader 读取程序头表
  • ReserveAddressSpace 准备地址空间
  • LoadSegments 加载段
  • FindPhdr 寻找Phdr段

从函数名直译,我们也能知道一个大概。下面我们来分析这6个函数
```c++
bool ElfReader::ReadElfHeader() {
//从我们打开的So文件中,读取header长度的内容赋值到header_
ssize_t rc = TEMP_FAILURERETRY(read(fd, &header, sizeof(header)));
if (rc < 0) {
DLERR("can't read file \"%s\": %s", name, strerror(errno));
return false;
}
if (rc != sizeof(header_)) {
DLERR("\"%s\" is too small to be an ELF executable", name);
return false;
}
return true;
}


```c++
bool ElfReader::VerifyElfHeader() {
  //校验魔数
  if (header_.e_ident[EI_MAG0] != ELFMAG0 ||
      header_.e_ident[EI_MAG1] != ELFMAG1 ||
      header_.e_ident[EI_MAG2] != ELFMAG2 ||
      header_.e_ident[EI_MAG3] != ELFMAG3) {
    DL_ERR("\"%s\" has bad ELF magic", name_);
    return false;
  }
  //因为分析的是Android4.4源码,所以它必须是一个32位的So
  if (header_.e_ident[EI_CLASS] != ELFCLASS32) {
    DL_ERR("\"%s\" not 32-bit: %d", name_, header_.e_ident[EI_CLASS]);
    return false;
  }
  //必须为小端字节序
  if (header_.e_ident[EI_DATA] != ELFDATA2LSB) {
    DL_ERR("\"%s\" not little-endian: %d", name_, header_.e_ident[EI_DATA]);
    return false;
  }
  //必须为ET_DYN,也就是我们的Shared Object So文件
  if (header_.e_type != ET_DYN) {
    DL_ERR("\"%s\" has unexpected e_type: %d", name_, header_.e_type);
    return false;
  }
  // version当前版本,这个一般都是EV_CURRENT(1)
  if (header_.e_version != EV_CURRENT) {
    DL_ERR("\"%s\" has unexpected e_version: %d", name_, header_.e_version);
    return false;
  }
  // 校验e_machine
  if (header_.e_machine !=
#ifdef ANDROID_ARM_LINKER
      EM_ARM
#elif defined(ANDROID_MIPS_LINKER)
      EM_MIPS
#elif defined(ANDROID_X86_LINKER)
      EM_386
#endif
  ) {
    DL_ERR("\"%s\" has unexpected e_machine: %d", name_, header_.e_machine);
    return false;
  }
  return true;
}

```c++
bool ElfReader::ReadProgramHeader() {
//e_phnum 为我们So的程序头表(段)数,后面我们都叫段,是一个意思
phdrnum = header_.e_phnum;

// Like the kernel, we only accept program header tables that
// are smaller than 64KiB.
if (phdrnum < 1 || phdrnum > 65536/sizeof(Elf32_Phdr)) {
DL_ERR("\"%s\" has invalid ephnum: %d", name, phdrnum);
return false;
}
//e_phoff 为我们段表在文件中的偏移, 然后进行内存页对其
Elf32_Addr page_min = PAGESTART(header.e_phoff);
Elf32_Addr page_max = PAGEEND(header.e_phoff + (phdrnum * sizeof(Elf32_Phdr)));
Elf32_Addr page_offset = PAGEOFFSET(header.e_phoff);
// 在Linux中内存读写都是以页为单位,所以上面按照存放段的位置,算出了需要的页大小
// page_offset就是段在该页的一个偏移

phdrsize = page_max - page_min;
//将该包含段表的页映射到内存
void* mmap_result = mmap(NULL, phdrsize, PROT_READ, MAPPRIVATE, fd, page_min);
if (mmap_result == MAP_FAILED) {
DLERR("\"%s\" phdr mmap failed: %s", name, strerror(errno));
return false;
}

phdrmmap = mmap_result;
// phdrtable 就指向了段表在内存中的起始位置
phdrtable = reinterpret_cast<Elf32_Phdr>(reinterpret_cast<char>(mmap_result) + page_offset);
return true;
}


```c++
bool ElfReader::ReserveAddressSpace() {
  //此时段表已经被加载到内存
  Elf32_Addr min_vaddr;
  //先获取该So的load_size,也就是需要加载的大小,先看下面对该函数的解释
  load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
  if (load_size_ == 0) {
    DL_ERR("\"%s\" has no loadable segments", name_);
    return false;
  }

  uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
  int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
  //根据PT_LOAD段的指示,匿名映射一块足够装下我们So的内存
  void* start = mmap(addr, load_size_, PROT_NONE, mmap_flags, -1, 0);
  if (start == MAP_FAILED) {
    DL_ERR("couldn't reserve %d bytes of address space for \"%s\"", load_size_, name_);
    return false;
  }
  load_start_ = start;
  //这里需要注意一下,start为我们映射出来的那块内存的起始地址,按理说它就是我们So加载的一个起始地址
  //那这里又计算了一个load_bias_是什么意思呢?
  //So文件并没有对p_vaddr有特殊要求,所以它可以是任意地址,如果它指定了一个最小的虚拟地址不为0
  //那么文件中的关于地址的引用就是根据它指定的虚拟地址来的
  //所以我们在后面进行对地址修正的时候,就要计算 start - min_addr来得到正确的值
  //所以这里计算了load_bias_, 后面关于地址引用的地方,我们都用这个load_bias_就可以了
  //举个例子:假设一个So中的PT_LOAD段指定的最小虚拟地址min_vaddr = 0x100
  //那么如果这个So中的一个函数中引用了一个地址为0x300地方的字符串
  //那这个字符串在实际文件中的偏移就是0x200 = 0x300 - 0x100
  //当So加载到内存中,需要对这个函数中的引用做重定位的时候,就应该这样计算
  //start + 0x300 - 0x100 <==> start - 0x100  + 0x300 
  //每次在计算的时候都要-0x100,所以这里就计算了一个load_bias_ = start - 0x100
  //后面直接用这个load_bias_ + 0x300(地址引用偏移) 就可以了
  load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
  return true;
}

```c++
size_t phdr_table_get_load_size(const Elf32_Phdr phdr_table,
size_t phdr_count,
Elf32_Addr
out_min_vaddr,
Elf32_Addr* out_max_vaddr)
{
Elf32_Addr min_vaddr = 0xFFFFFFFFU;
Elf32_Addr max_vaddr = 0x00000000U;

bool found_pt_load = false;
//遍历我们的段表
for (size_t i = 0; i < phdr_count; ++i) {
    const Elf32_Phdr* phdr = &phdr_table[i];
    //只处理PT_LOAD段,因为PT_LOAD段说明了我们的So应该怎么加载
    if (phdr->p_type != PT_LOAD) {
        continue;
    }
    found_pt_load = true;
    //遍历所有的PT_LOAD段,寻找So指定的最小的一个虚拟地址
    if (phdr->p_vaddr < min_vaddr) {
        min_vaddr = phdr->p_vaddr;
    }
    //遍历所有的PT_LOAD段,寻找So指定的要加载到内存的最大的一个虚拟地址
    if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
        max_vaddr = phdr->p_vaddr + phdr->p_memsz;
    }
}
if (!found_pt_load) {
    min_vaddr = 0x00000000U;
}
//So文件并没有对p_vaddr有特殊要求,所以这里需要页对齐
min_vaddr = PAGE_START(min_vaddr);
max_vaddr = PAGE_END(max_vaddr);

if (out_min_vaddr != NULL) {
    *out_min_vaddr = min_vaddr;
}
if (out_max_vaddr != NULL) {
    *out_max_vaddr = max_vaddr;
}
//最大-最小拿到该So加载到内存的一个size
return max_vaddr - min_vaddr;

}


```c++
//上面ReserveAddressSpace函数,只是开辟了一块足够的内存,并没有内容
//这个函数就在填充内容
bool ElfReader::LoadSegments() {
  for (size_t i = 0; i < phdr_num_; ++i) {
    const Elf32_Phdr* phdr = &phdr_table_[i];
    //还是在遍历每一个PT_LOAD段
    if (phdr->p_type != PT_LOAD) {
      continue;
    }

    // 计算该PT_LOAD段在内存中的开始地址和结束地址
    Elf32_Addr seg_start = phdr->p_vaddr + load_bias_;
    Elf32_Addr seg_end   = seg_start + phdr->p_memsz;

    Elf32_Addr seg_page_start = PAGE_START(seg_start);
    Elf32_Addr seg_page_end   = PAGE_END(seg_end);
    //计算该PT_LOAD段在内存中对应文件的结束位置
    Elf32_Addr seg_file_end   = seg_start + phdr->p_filesz;

    //文件中的偏移
    Elf32_Addr file_start = phdr->p_offset;
    Elf32_Addr file_end   = file_start + phdr->p_filesz;

    Elf32_Addr file_page_start = PAGE_START(file_start);
    Elf32_Addr file_length = file_end - file_page_start;

    if (file_length != 0) {
      //将该PT_LOAD段的实际内容页对齐后映射到内存中
      void* seg_addr = mmap((void*)seg_page_start,
                            file_length,
                            PFLAGS_TO_PROT(phdr->p_flags),
                            MAP_FIXED|MAP_PRIVATE,
                            fd_,
                            file_page_start);
      if (seg_addr == MAP_FAILED) {
        DL_ERR("couldn't map \"%s\" segment %d: %s", name_, i, strerror(errno));
        return false;
      }
    }

    //如果该段的权限可写且该段指定的文件大小并不是页边界对齐的,就要对页内没有文件与之对应的区域置0
    if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
      memset((void*)seg_file_end, 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
    }

    seg_file_end = PAGE_END(seg_file_end);

    // 如果该段指定的内存大小超出了文件映射的页面,就要对多出的页进行匿名映射
    // 防止出现Bus error的情况
    if (seg_page_end > seg_file_end) {
      void* zeromap = mmap((void*)seg_file_end,
                           seg_page_end - seg_file_end,
                           PFLAGS_TO_PROT(phdr->p_flags),
                           MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
                           -1,
                           0);
      if (zeromap == MAP_FAILED) {
        DL_ERR("couldn't zero fill \"%s\" gap: %s", name_, strerror(errno));
        return false;
      }
    }
  }
  return true;
}

```c++
//这个函数看看就可以,就是在找PT_PHDR段并检查,此段指定了段表本身的位置和大小
bool ElfReader::FindPhdr() {
const Elf32_Phdr* phdr_limit = phdrtable + phdrnum;

// If there is a PT_PHDR, use it directly.
for (const Elf32_Phdr phdr = phdrtable; phdr < phdr_limit; ++phdr) {
if (phdr->p_type == PT_PHDR) {
return CheckPhdr(loadbias + phdr->p_vaddr);
}
}
for (const Elf32_Phdr
phdr = phdrtable; phdr < phdr_limit; ++phdr) {
if (phdr->p_type == PT_LOAD) {
if (phdr->p_offset == 0) {
Elf32_Addr  elf_addr = loadbias + phdr->p_vaddr;
const Elf32_Ehdr ehdr = (const Elf32_Ehdr)(void*)elf_addr;
Elf32_Addr  offset = ehdr->e_phoff;
return CheckPhdr((Elf32_Addr)ehdr + offset);
}
break;
}
}
DLERR("can't find loaded phdr for \"%s\"", name);
return false;
}


至此 So的装载部分就分析完了

## 总结

总结一下So的装载就是根据So的文件信息,先读入So的头部信息,并进行验证。然后找到段表的位置,遍历段表的每一个段,根据PT_LOAD段指定的信息将So进行装载,如果我们要模拟这个过程,只需要注意一下细节就可以了。相对于So的装载,更难的部分是So的动态链接,我们另起一篇文章来讲解So的动态链接。

免费评分

参与人数 6吾爱币 +4 热心值 +6 收起 理由
zhaihaoran123 + 1 + 1 热心回复!
JPK + 1 热心回复!
CWJYBB + 1 + 1 很厉害,先收藏 入门的能学会吗
lgc81034 + 1 谢谢@Thanks!
lingyun011 + 1 + 1 热心回复!
Ack麦子 + 1 + 1 比心肉丝姐

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Ack麦子 发表于 2021-10-15 19:48
姐姐好  求翻
 楼主| roysue 发表于 2021-10-20 17:31
hjw01 发表于 2021-10-15 20:35
斧正楼主举例的load_bias_是用于文件地址偏移和虚拟地址偏移的差值的意思。
按官方描述是减少内存空间浪 ...

感谢大佬的斧正,[
斧正楼主举例的load_bias_是用于文件地址偏移和虚拟地址偏移的差值的意思]我文中是认为它是申请地址空间的地址和最小虚拟地址的偏移
当时看这里也很疑惑,找了一个自己能理解的点来理解,可能跟实际意思有些偏差
不过看您的回答我也是一下就理解了,可能您的这个回答更符合官方的意思
hjw01 发表于 2021-10-15 20:35
斧正楼主举例的load_bias_是用于文件地址偏移和虚拟地址偏移的差值的意思。
按官方描述是减少内存空间浪费。已知两个空间虚拟地址:0x30000...0x34000,0x40000...0x48000
如果申请的地址空间是 0xa0000000,那么加载范围是: 0xa0030000...0xa0034000,0xa0040000...0xa0048000
那么范围:0xa0000000-0xa0030000将被浪费。通过 load_bias_=0xa0000000-0x30000得到偏差值,使得加载的内存地址由0xa0030000,变为0xa0000000
Elf32_Addr seg_page_start = PAGE_START(phdr->p_vaddr + load_bias_);
void* seg_addr = mmap((void*)seg_page_start,file_length,...fd_, file_page_start); //此时seg_page_start=0xa0000000,也就是段加载的地址
andyup2 发表于 2021-10-15 20:01
学习了。。这段时间在学Android
魔-沫 发表于 2021-10-16 00:24
感谢楼主分享
goda 发表于 2021-10-16 21:27
感谢分享
pplus 发表于 2021-10-17 08:15
有用。谢谢哈
dbgcode 发表于 2021-10-17 19:40
好帖,谢谢楼主分享,MARK下。。。
wangxon 发表于 2021-10-20 11:12
感谢分享。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 17:15

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表