├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── migrations.xml └── misc.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ng1ok │ │ └── linker │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── MyLoader.cpp │ │ ├── MyLoader.h │ │ ├── log.h │ │ ├── native-lib.cpp │ │ └── soinfo.h │ ├── java │ │ └── ng1ok │ │ │ └── linker │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── ng1ok │ └── linker │ └── ExampleUnitTest.java ├── build.gradle ├── demo1 ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ng1ok │ │ └── demo1 │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── demo1.cpp │ └── java │ │ └── ng1ok │ │ └── demo1 │ │ └── NativeLib.java │ └── test │ └── java │ └── ng1ok │ └── demo1 │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Ng1okLinker -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 項目介紹 2 | - https://bbs.kanxue.com/thread-282316.htm 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'ng1ok.linker' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | applicationId "ng1ok.linker" 11 | minSdk 24 12 | targetSdk 34 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | externalNativeBuild { 30 | cmake { 31 | path file('src/main/cpp/CMakeLists.txt') 32 | version '3.22.1' 33 | } 34 | } 35 | buildFeatures { 36 | viewBinding true 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | implementation 'androidx.appcompat:appcompat:1.6.1' 43 | implementation 'com.google.android.material:material:1.12.0' 44 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 45 | testImplementation 'junit:junit:4.13.2' 46 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 48 | 49 | // implementation project(path: ":demo1") 50 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/ng1ok/linker/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ng1ok.linker; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("ng1ok.linker", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | project("nglinker") 9 | 10 | include_directories( 11 | ./ 12 | ) 13 | 14 | 15 | add_library(${CMAKE_PROJECT_NAME} SHARED 16 | # List C/C++ source files with relative paths to this CMakeLists.txt. 17 | native-lib.cpp 18 | MyLoader.cpp 19 | ) 20 | 21 | 22 | target_link_libraries(${CMAKE_PROJECT_NAME} 23 | # List libraries link to the target library 24 | android 25 | log) -------------------------------------------------------------------------------- /app/src/main/cpp/MyLoader.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by user on 2024/6/15. 3 | // 4 | #include "MyLoader.h" 5 | 6 | int myneed[20]; 7 | uint32_t needed_count = 0; 8 | 9 | const char* get_realpath() { 10 | return ""; 11 | } 12 | 13 | 14 | int Utils::phdr_table_set_gnu_relro_prot(const ElfW(Phdr)* phdr_table, size_t phdr_count, 15 | ElfW(Addr) load_bias, int prot_flags) { 16 | const ElfW(Phdr)* phdr = phdr_table; 17 | const ElfW(Phdr)* phdr_limit = phdr + phdr_count; 18 | 19 | for (phdr = phdr_table; phdr < phdr_limit; phdr++) { 20 | if (phdr->p_type != PT_GNU_RELRO) { 21 | continue; 22 | } 23 | ElfW(Addr) seg_page_start = PAGE_START(phdr->p_vaddr) + load_bias; 24 | ElfW(Addr) seg_page_end = PAGE_END(phdr->p_vaddr + phdr->p_memsz) + load_bias; 25 | 26 | int ret = mprotect(reinterpret_cast(seg_page_start), 27 | seg_page_end - seg_page_start, 28 | prot_flags); 29 | if (ret < 0) { 30 | return -1; 31 | } 32 | } 33 | return 0; 34 | } 35 | 36 | size_t Utils::page_offset(off64_t offset) { 37 | return static_cast(offset & (PAGE_SIZE-1)); 38 | } 39 | 40 | off64_t Utils::page_start(off64_t offset) { 41 | 42 | return offset & kPageMask; 43 | } 44 | 45 | bool Utils::safe_add(off64_t* out, off64_t a, size_t b) { 46 | if (static_cast(INT64_MAX - a) < b) { 47 | return false; 48 | } 49 | 50 | *out = a + b; 51 | return true; 52 | } 53 | 54 | void* Utils::getMapData(int fd, off64_t base_offset, size_t elf_offset, size_t size) { 55 | off64_t offset; 56 | safe_add(&offset, base_offset, elf_offset); 57 | 58 | off64_t page_min = page_start(offset); 59 | off64_t end_offset; 60 | 61 | safe_add(&end_offset, offset, size); 62 | safe_add(&end_offset, end_offset, page_offset(offset)); 63 | 64 | size_t map_size = static_cast(end_offset - page_min); 65 | 66 | uint8_t* map_start = static_cast( 67 | mmap64(nullptr, map_size, PROT_READ, MAP_PRIVATE, fd, page_min)); 68 | 69 | if (map_start == MAP_FAILED) { 70 | return nullptr; 71 | } 72 | 73 | return map_start + page_offset(offset); 74 | 75 | } 76 | 77 | void Utils::phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, size_t phdr_count, 78 | ElfW(Addr) load_bias, ElfW(Dyn)** dynamic, 79 | ElfW(Word)* dynamic_flags) { 80 | *dynamic = nullptr; 81 | for (size_t i = 0; i(load_bias + phdr.p_vaddr); 85 | if (dynamic_flags) { 86 | *dynamic_flags = phdr.p_flags; 87 | } 88 | return; 89 | } 90 | } 91 | } 92 | 93 | 94 | ElfW(Addr) Utils::get_export_func(char* path, char* func_name) { 95 | 96 | struct stat sb; 97 | int fd = open(path, O_RDONLY); 98 | fstat(fd, &sb); 99 | void* base = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); 100 | 101 | // 讀取elf header 102 | ElfW(Ehdr) header; 103 | memcpy(&(header), base, sizeof(header)); 104 | 105 | // 讀取Section header table 106 | size_t size = header.e_shnum * sizeof(ElfW(Shdr)); 107 | void* tmp = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 注: 必須要 MAP_ANONYMOUS 108 | LOGD("error: %s", strerror(errno)); 109 | ElfW(Shdr)* shdr_table; 110 | memcpy(tmp, (void*)((ElfW(Off))base + header.e_shoff), size); 111 | shdr_table = static_cast(tmp); 112 | 113 | char* shstrtab = reinterpret_cast(shdr_table[header.e_shstrndx].sh_offset + (ElfW(Off))base); 114 | 115 | void* symtab = nullptr; 116 | char* strtab = nullptr; 117 | uint32_t symtab_size = 0; 118 | 119 | // 遍歷獲取.symtab和.strtab節 120 | for (size_t i = 0; i < header.e_shnum; ++i) { 121 | const ElfW(Shdr) *shdr = &shdr_table[i]; 122 | char* section_name = shstrtab + shdr->sh_name; 123 | if(!strcmp(section_name, ".symtab")) { 124 | // LOGD("[test] %d: shdr->sh_name = %s", i, (shstrtab + shdr->sh_name)); 125 | symtab = reinterpret_cast(shdr->sh_offset + (ElfW(Off))base); 126 | symtab_size = shdr->sh_size; 127 | } 128 | if(!strcmp(section_name, ".strtab")) { 129 | // LOGD("[test] %d: shdr->sh_name = %s", i, (shstrtab + shdr->sh_name)); 130 | strtab = reinterpret_cast(shdr->sh_offset + (ElfW(Off))base); 131 | } 132 | 133 | if(strtab && symtab)break; 134 | } 135 | 136 | // 讀取 Symbol table 137 | ElfW(Sym)* sym_table; 138 | tmp = mmap(nullptr, symtab_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 139 | memcpy(tmp, symtab, symtab_size); 140 | sym_table = static_cast(tmp); 141 | 142 | int sym_num = symtab_size / sizeof(ElfW(Sym)); 143 | 144 | // 遍歷 Symbol table 145 | for(int i = 0; i < sym_num; i++) { 146 | const ElfW(Sym) *sym = &sym_table[i]; 147 | char* sym_name = strtab + sym->st_name; 148 | if(strstr(sym_name, func_name)) { 149 | return sym->st_value; 150 | } 151 | 152 | 153 | } 154 | 155 | 156 | return 0; 157 | } 158 | 159 | soinfo* Utils::get_soinfo(const char* so_name) { 160 | typedef soinfo* (*FunctionPtr)(ElfW(Addr)); 161 | 162 | char line[1024]; 163 | ElfW(Addr) linker_base = 0; 164 | ElfW(Addr) so_addr = 0; 165 | FILE *fp=fopen("/proc/self/maps","r"); 166 | while (fgets(line, sizeof(line), fp)) { 167 | if (strstr(line, "linker64") && !linker_base) { 168 | char* addr = strtok(line, "-"); 169 | linker_base = strtoull(addr, NULL, 16); 170 | 171 | }else if(strstr(line, so_name) && !so_addr) { 172 | char* addr = strtok(line, "-"); 173 | so_addr = strtoull(addr, NULL, 16); 174 | 175 | } 176 | 177 | if(linker_base && so_addr)break; 178 | 179 | } 180 | 181 | 182 | ElfW(Addr) func_offset = Utils::get_export_func("/system/bin/linker64", "find_containing_library"); 183 | if(!func_offset) { 184 | LOGE("func_offset == 0? check it ---> get_soinfo"); 185 | return nullptr; 186 | } 187 | // ElfW(Addr) find_containing_library_addr = static_cast(linker_base + 0x9AB0); 188 | ElfW(Addr) find_containing_library_addr = static_cast(linker_base + func_offset); 189 | FunctionPtr find_containing_library = reinterpret_cast(find_containing_library_addr); 190 | 191 | return find_containing_library(so_addr); 192 | } 193 | 194 | ElfW(Addr) Utils::call_ifunc_resolver(ElfW(Addr) resolver_addr) { 195 | typedef ElfW(Addr) (*ifunc_resolver_t)(void); 196 | ifunc_resolver_t ifunc_resolver = reinterpret_cast(resolver_addr); 197 | ElfW(Addr) ifunc_addr = ifunc_resolver(); 198 | 199 | return ifunc_addr; 200 | } 201 | 202 | 203 | ElfW(Addr) Utils::get_addend(ElfW(Rela)* rela, ElfW(Addr) reloc_addr __unused) { 204 | return rela->r_addend; 205 | } 206 | 207 | 208 | const char* soinfo::get_realpath() const { 209 | return ""; 210 | } 211 | 212 | const char* soinfo::get_string(ElfW(Word) index) const { 213 | return strtab_ + index; 214 | } 215 | 216 | void soinfo::set_dt_flags_1(uint32_t dt_flags_1) { 217 | if (has_min_version(1)) { 218 | if ((dt_flags_1 & DF_1_GLOBAL) != 0) { 219 | rtld_flags_ |= RTLD_GLOBAL; 220 | } 221 | 222 | if ((dt_flags_1 & DF_1_NODELETE) != 0) { 223 | rtld_flags_ |= RTLD_NODELETE; 224 | } 225 | 226 | dt_flags_1_ = dt_flags_1; 227 | } 228 | } 229 | 230 | 231 | bool soinfo::prelink_image() { 232 | /* Extract dynamic section */ 233 | ElfW(Word) dynamic_flags = 0; 234 | Utils::phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags); 235 | 236 | if (dynamic == nullptr) { 237 | return false; 238 | } else { 239 | } 240 | 241 | 242 | // uint32_t needed_count = 0; 243 | for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) { 244 | LOGD("d = %p, d[0](tag) = %p d[1](val) = %p", 245 | d, reinterpret_cast(d->d_tag), reinterpret_cast(d->d_un.d_val)); 246 | switch (d->d_tag) { 247 | case DT_SONAME: 248 | // this is parsed after we have strtab initialized (see below). 249 | break; 250 | 251 | case DT_HASH: 252 | nbucket_ = reinterpret_cast(load_bias + d->d_un.d_ptr)[0]; 253 | nchain_ = reinterpret_cast(load_bias + d->d_un.d_ptr)[1]; 254 | bucket_ = reinterpret_cast(load_bias + d->d_un.d_ptr + 8); 255 | chain_ = reinterpret_cast(load_bias + d->d_un.d_ptr + 8 + nbucket_ * 4); 256 | break; 257 | 258 | case DT_GNU_HASH: { 259 | 260 | gnu_nbucket_ = reinterpret_cast(load_bias + d->d_un.d_ptr)[0]; 261 | // skip symndx 262 | gnu_maskwords_ = reinterpret_cast(load_bias + d->d_un.d_ptr)[2]; 263 | gnu_shift2_ = reinterpret_cast(load_bias + d->d_un.d_ptr)[3]; 264 | 265 | gnu_bloom_filter_ = reinterpret_cast(load_bias + d->d_un.d_ptr + 16); 266 | gnu_bucket_ = reinterpret_cast(gnu_bloom_filter_ + gnu_maskwords_); 267 | // amend chain for symndx = header[1] 268 | gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - 269 | reinterpret_cast(load_bias + d->d_un.d_ptr)[1]; 270 | 271 | 272 | if (!powerof2(gnu_maskwords_)) { 273 | LOGE("invalid maskwords for gnu_hash = 0x%x, in \"%s\" expecting power to two", 274 | gnu_maskwords_, ""); 275 | return false; 276 | } 277 | --gnu_maskwords_; 278 | 279 | flags_ |= FLAG_GNU_HASH; 280 | 281 | 282 | break; 283 | } 284 | case DT_STRTAB: 285 | strtab_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 286 | break; 287 | 288 | case DT_STRSZ: 289 | strtab_size_ = d->d_un.d_val; 290 | break; 291 | 292 | case DT_SYMTAB: 293 | symtab_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 294 | break; 295 | 296 | case DT_SYMENT: 297 | if (d->d_un.d_val != sizeof(ElfW(Sym))) { 298 | LOGD("invalid DT_SYMENT: %zd in \"%s\"", 299 | static_cast(d->d_un.d_val), ""); 300 | return false; 301 | } 302 | break; 303 | 304 | case DT_PLTREL: 305 | #if defined(USE_RELA) 306 | if (d->d_un.d_val != DT_RELA) { 307 | LOGD("unsupported DT_PLTREL in \"%s\"; expected DT_RELA", get_realpath()); 308 | return false; 309 | } 310 | #else 311 | if (d->d_un.d_val != DT_REL) { 312 | LOGD("unsupported DT_PLTREL in \"%s\"; expected DT_REL", ""); 313 | LOGD("d->d_un.d_val = %x", d->d_un.d_val); 314 | return false; 315 | } 316 | #endif 317 | break; 318 | 319 | case DT_JMPREL: 320 | #if defined(USE_RELA) 321 | plt_rela_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 322 | #else 323 | plt_rel_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 324 | #endif 325 | break; 326 | 327 | case DT_PLTRELSZ: 328 | #if defined(USE_RELA) 329 | plt_rela_count_ = d->d_un.d_val / sizeof(ElfW(Rela)); 330 | #else 331 | plt_rel_count_ = d->d_un.d_val / sizeof(ElfW(Rel)); 332 | #endif 333 | break; 334 | 335 | case DT_PLTGOT: 336 | #if defined(__mips__) 337 | // Used by mips and mips64. 338 | plt_got_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 339 | #endif 340 | // Ignore for other platforms... (because RTLD_LAZY is not supported) 341 | break; 342 | 343 | case DT_DEBUG: 344 | // Set the DT_DEBUG entry to the address of _r_debug for GDB 345 | // if the dynamic table is writable 346 | // FIXME: not working currently for N64 347 | // The flags for the LOAD and DYNAMIC program headers do not agree. 348 | // The LOAD section containing the dynamic table has been mapped as 349 | // read-only, but the DYNAMIC header claims it is writable. 350 | #if !(defined(__mips__) && defined(__LP64__)) 351 | if ((dynamic_flags & PF_W) != 0) { 352 | LOGD("pass code: d->d_un.d_val = reinterpret_cast(&_r_debug);"); 353 | // d->d_un.d_val = reinterpret_cast(&_r_debug); 354 | } 355 | #endif 356 | break; 357 | #if defined(USE_RELA) 358 | case DT_RELA: 359 | rela_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 360 | break; 361 | 362 | case DT_RELASZ: 363 | rela_count_ = d->d_un.d_val / sizeof(ElfW(Rela)); 364 | break; 365 | 366 | case DT_ANDROID_RELA: 367 | android_relocs_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 368 | break; 369 | 370 | case DT_ANDROID_RELASZ: 371 | android_relocs_size_ = d->d_un.d_val; 372 | break; 373 | 374 | case DT_ANDROID_REL: 375 | LOGD("unsupported DT_ANDROID_REL in \"%s\"", get_realpath()); 376 | return false; 377 | 378 | case DT_ANDROID_RELSZ: 379 | LOGD("unsupported DT_ANDROID_RELSZ in \"%s\"", get_realpath()); 380 | return false; 381 | 382 | case DT_RELAENT: 383 | if (d->d_un.d_val != sizeof(ElfW(Rela))) { 384 | LOGD("invalid DT_RELAENT: %zd", static_cast(d->d_un.d_val)); 385 | return false; 386 | } 387 | break; 388 | 389 | // ignored (see DT_RELCOUNT comments for details) 390 | case DT_RELACOUNT: 391 | break; 392 | 393 | case DT_REL: 394 | LOGD("unsupported DT_REL in \"%s\"", get_realpath()); 395 | return false; 396 | 397 | case DT_RELSZ: 398 | LOGD("unsupported DT_RELSZ in \"%s\"", get_realpath()); 399 | return false; 400 | 401 | #else 402 | case DT_REL: 403 | rel_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 404 | break; 405 | 406 | case DT_RELSZ: 407 | rel_count_ = d->d_un.d_val / sizeof(ElfW(Rel)); 408 | break; 409 | 410 | case DT_RELENT: 411 | if (d->d_un.d_val != sizeof(ElfW(Rel))) { 412 | LOGD("invalid DT_RELENT: %zd", static_cast(d->d_un.d_val)); 413 | return false; 414 | } 415 | break; 416 | 417 | case DT_ANDROID_REL: 418 | android_relocs_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 419 | break; 420 | 421 | case DT_ANDROID_RELSZ: 422 | android_relocs_size_ = d->d_un.d_val; 423 | break; 424 | 425 | case DT_ANDROID_RELA: 426 | LOGD("unsupported DT_ANDROID_RELA in \"%s\"", ""); 427 | return false; 428 | 429 | case DT_ANDROID_RELASZ: 430 | LOGD("unsupported DT_ANDROID_RELASZ in \"%s\"", ""); 431 | return false; 432 | 433 | // "Indicates that all RELATIVE relocations have been concatenated together, 434 | // and specifies the RELATIVE relocation count." 435 | // 436 | // TODO: Spec also mentions that this can be used to optimize relocation process; 437 | // Not currently used by bionic linker - ignored. 438 | case DT_RELCOUNT: 439 | break; 440 | 441 | case DT_RELA: 442 | LOGD("unsupported DT_RELA in \"%s\"", ""); 443 | return false; 444 | 445 | case DT_RELASZ: 446 | LOGD("unsupported DT_RELASZ in \"%s\"", ""); 447 | return false; 448 | 449 | #endif 450 | case DT_INIT: 451 | init_func_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 452 | LOGD("%s constructors (DT_INIT) found at %p", get_realpath(), init_func_); 453 | break; 454 | 455 | case DT_FINI: 456 | fini_func_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 457 | LOGD("%s destructors (DT_FINI) found at %p", get_realpath(), fini_func_); 458 | break; 459 | 460 | case DT_INIT_ARRAY: 461 | init_array_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 462 | LOGD("%s constructors (DT_INIT_ARRAY) found at %p", get_realpath(), init_array_); 463 | break; 464 | 465 | case DT_INIT_ARRAYSZ: 466 | init_array_count_ = static_cast(d->d_un.d_val) / sizeof(ElfW(Addr)); 467 | break; 468 | 469 | case DT_FINI_ARRAY: 470 | fini_array_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 471 | LOGD("%s destructors (DT_FINI_ARRAY) found at %p", get_realpath(), fini_array_); 472 | break; 473 | 474 | case DT_FINI_ARRAYSZ: 475 | fini_array_count_ = static_cast(d->d_un.d_val) / sizeof(ElfW(Addr)); 476 | break; 477 | 478 | case DT_PREINIT_ARRAY: 479 | preinit_array_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 480 | LOGD("%s constructors (DT_PREINIT_ARRAY) found at %p", get_realpath(), preinit_array_); 481 | break; 482 | 483 | case DT_PREINIT_ARRAYSZ: 484 | preinit_array_count_ = static_cast(d->d_un.d_val) / sizeof(ElfW(Addr)); 485 | break; 486 | 487 | case DT_TEXTREL: 488 | #if defined(__LP64__) 489 | LOGD("\"%s\" has text relocations", get_realpath()); 490 | return false; 491 | #else 492 | has_text_relocations = true; 493 | break; 494 | #endif 495 | 496 | case DT_SYMBOLIC: 497 | has_DT_SYMBOLIC = true; 498 | break; 499 | 500 | case DT_NEEDED: 501 | // 手動保留所有依賴庫, 用於之後的重定位 502 | myneed[needed_count] = d->d_un.d_val; 503 | ++needed_count; 504 | break; 505 | 506 | case DT_FLAGS: 507 | if (d->d_un.d_val & DF_TEXTREL) { 508 | #if defined(__LP64__) 509 | LOGD("\"%s\" has text relocations", get_realpath()); 510 | return false; 511 | #else 512 | has_text_relocations = true; 513 | #endif 514 | } 515 | if (d->d_un.d_val & DF_SYMBOLIC) { 516 | has_DT_SYMBOLIC = true; 517 | } 518 | break; 519 | 520 | case DT_FLAGS_1: 521 | // LOGE("in case DT_FLAGS_1:"); 522 | set_dt_flags_1(d->d_un.d_val); 523 | 524 | if ((d->d_un.d_val & ~SUPPORTED_DT_FLAGS_1) != 0) { 525 | LOGE("\"%s\" has unsupported flags DT_FLAGS_1=%p", get_realpath(), reinterpret_cast(d->d_un.d_val)); 526 | } 527 | break; 528 | #if defined(__mips__) 529 | case DT_MIPS_RLD_MAP: 530 | // Set the DT_MIPS_RLD_MAP entry to the address of _r_debug for GDB. 531 | { 532 | r_debug** dp = reinterpret_cast(load_bias + d->d_un.d_ptr); 533 | *dp = &_r_debug; 534 | } 535 | break; 536 | case DT_MIPS_RLD_MAP_REL: 537 | // Set the DT_MIPS_RLD_MAP_REL entry to the address of _r_debug for GDB. 538 | { 539 | r_debug** dp = reinterpret_cast( 540 | reinterpret_cast(d) + d->d_un.d_val); 541 | *dp = &_r_debug; 542 | } 543 | break; 544 | 545 | case DT_MIPS_RLD_VERSION: 546 | case DT_MIPS_FLAGS: 547 | case DT_MIPS_BASE_ADDRESS: 548 | case DT_MIPS_UNREFEXTNO: 549 | break; 550 | 551 | case DT_MIPS_SYMTABNO: 552 | mips_symtabno_ = d->d_un.d_val; 553 | break; 554 | 555 | case DT_MIPS_LOCAL_GOTNO: 556 | mips_local_gotno_ = d->d_un.d_val; 557 | break; 558 | 559 | case DT_MIPS_GOTSYM: 560 | mips_gotsym_ = d->d_un.d_val; 561 | break; 562 | #endif 563 | // Ignored: "Its use has been superseded by the DF_BIND_NOW flag" 564 | case DT_BIND_NOW: 565 | break; 566 | 567 | case DT_VERSYM: 568 | versym_ = reinterpret_cast(load_bias + d->d_un.d_ptr); 569 | break; 570 | 571 | case DT_VERDEF: 572 | verdef_ptr_ = load_bias + d->d_un.d_ptr; 573 | break; 574 | case DT_VERDEFNUM: 575 | verdef_cnt_ = d->d_un.d_val; 576 | break; 577 | 578 | case DT_VERNEED: 579 | verneed_ptr_ = load_bias + d->d_un.d_ptr; 580 | break; 581 | 582 | case DT_VERNEEDNUM: 583 | verneed_cnt_ = d->d_un.d_val; 584 | break; 585 | 586 | case DT_RUNPATH: 587 | // this is parsed after we have strtab initialized (see below). 588 | break; 589 | 590 | default: 591 | LOGE("in default:"); 592 | // if (!relocating_linker) { 593 | // DL_WARN("\"%s\" unused DT entry: type %p arg %p", get_realpath(), 594 | // reinterpret_cast(d->d_tag), reinterpret_cast(d->d_un.d_val)); 595 | // } 596 | break; 597 | } 598 | } 599 | 600 | 601 | LOGD("si->base = %p, si->strtab = %p, si->symtab = %p", 602 | reinterpret_cast(base), strtab_, symtab_); 603 | 604 | if (nbucket_ == 0 && gnu_nbucket_ == 0) { 605 | LOGD("empty/missing DT_HASH/DT_GNU_HASH in \"%s\" " 606 | "(new hash type from the future?)", get_realpath()); 607 | return false; 608 | } 609 | if (strtab_ == 0) { 610 | LOGD("empty/missing DT_STRTAB in \"%s\"", get_realpath()); 611 | return false; 612 | } 613 | if (symtab_ == 0) { 614 | LOGD("empty/missing DT_SYMTAB in \"%s\"", get_realpath()); 615 | return false; 616 | } 617 | 618 | // second pass - parse entries relying on strtab 619 | for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) { 620 | switch (d->d_tag) { 621 | case DT_SONAME: 622 | soname_ = get_string(d->d_un.d_val); 623 | LOGD("set soname = %s", soname_); 624 | break; 625 | case DT_RUNPATH: 626 | // set_dt_runpath(get_string(d->d_un.d_val)); 627 | LOGD("set_dt_runpath(%s)", get_string(d->d_un.d_val)); 628 | break; 629 | } 630 | } 631 | 632 | return true; 633 | } 634 | 635 | 636 | bool soinfo::link_image() { 637 | local_group_root_ = this; 638 | 639 | if (android_relocs_ != nullptr) { 640 | LOGD("android_relocs_ 不用處理?"); 641 | 642 | } else { 643 | LOGE("bad android relocation header."); 644 | // return false; 645 | } 646 | 647 | 648 | #if defined(USE_RELA) 649 | if (rela_ != nullptr) { 650 | LOGD("[ relocating %s ]", get_realpath()); 651 | if (!relocate(plain_reloc_iterator(rela_, rela_count_))) { 652 | return false; 653 | } 654 | } 655 | if (plt_rela_ != nullptr) { 656 | LOGD("[ relocating %s plt ]", get_realpath()); 657 | if (!relocate(plain_reloc_iterator(plt_rela_, plt_rela_count_))) { 658 | return false; 659 | } 660 | } 661 | #else 662 | LOGE("TODO: !defined(USE_RELA) "); 663 | #endif 664 | 665 | LOGD("[ finished linking %s ]", get_realpath()); 666 | 667 | 668 | // We can also turn on GNU RELRO protection if we're not linking the dynamic linker 669 | // itself --- it can't make system calls yet, and will have to call protect_relro later. 670 | if (!((flags_ & FLAG_LINKER) != 0) && !protect_relro()) { 671 | return false; 672 | } 673 | 674 | return true; 675 | } 676 | 677 | 678 | 679 | template 680 | bool soinfo::relocate(ElfRelIteratorT&& rel_iterator) { 681 | for (size_t idx = 0; rel_iterator.has_next(); ++idx) { 682 | const auto rel = rel_iterator.next(); 683 | if (rel == nullptr) { 684 | return false; 685 | } 686 | 687 | 688 | ElfW(Word) type = ELFW(R_TYPE)(rel->r_info); 689 | ElfW(Word) sym = ELFW(R_SYM)(rel->r_info); 690 | 691 | // reloc 指向需要重定向的內容, 根據type來決定重定向成什麼 692 | ElfW(Addr) reloc = static_cast(rel->r_offset + load_bias); 693 | ElfW(Addr) sym_addr = 0; 694 | const char* sym_name = nullptr; 695 | ElfW(Addr) addend = Utils::get_addend(rel, reloc); 696 | 697 | // LOGD("Processing \"%s\" relocation at index %zd", get_realpath(), idx); 698 | if (type == R_GENERIC_NONE) { 699 | continue; 700 | } 701 | 702 | const ElfW(Sym)* s = nullptr; 703 | soinfo* lsi = nullptr; 704 | 705 | if (sym != 0) { 706 | 707 | sym_name = get_string(symtab_[sym].st_name); 708 | // LOGD("sym = %lx sym_name: %s st_value: %lx", sym, sym_name, symtab_[sym].st_value); 709 | 710 | 711 | for(int s = 0; s < needed_count; s++) { 712 | void* handle = dlopen(get_string(myneed[s]),RTLD_NOW); 713 | sym_addr = reinterpret_cast(dlsym(handle, sym_name)); 714 | if(sym_addr) break; 715 | 716 | } 717 | 718 | if(!sym_addr) { 719 | if(symtab_[sym].st_value != 0) { 720 | sym_addr = load_bias + symtab_[sym].st_value; 721 | }else { 722 | LOGE("%s find addr fail", sym_name); 723 | } 724 | 725 | }else { 726 | // LOGD("%s find addr success : %lx", sym_name, sym_addr); 727 | } 728 | } 729 | 730 | 731 | switch (type) { 732 | case R_GENERIC_JUMP_SLOT: 733 | *reinterpret_cast(reloc) = (sym_addr + addend); 734 | break; 735 | case R_GENERIC_GLOB_DAT: 736 | *reinterpret_cast(reloc) = (sym_addr + addend); 737 | break; 738 | case R_GENERIC_RELATIVE: 739 | *reinterpret_cast(reloc) = (load_bias + addend); 740 | break; 741 | case R_GENERIC_IRELATIVE: 742 | { 743 | 744 | ElfW(Addr) ifunc_addr = Utils::call_ifunc_resolver(load_bias + addend); 745 | *reinterpret_cast(reloc) = ifunc_addr; 746 | } 747 | break; 748 | 749 | #if defined(__aarch64__) 750 | case R_AARCH64_ABS64: 751 | *reinterpret_cast(reloc) = sym_addr + addend; 752 | break; 753 | case R_AARCH64_ABS32: 754 | { 755 | const ElfW(Addr) min_value = static_cast(INT32_MIN); 756 | const ElfW(Addr) max_value = static_cast(UINT32_MAX); 757 | if ((min_value <= (sym_addr + addend)) && 758 | ((sym_addr + addend) <= max_value)) { 759 | *reinterpret_cast(reloc) = sym_addr + addend; 760 | } else { 761 | LOGE("0x%016llx out of range 0x%016llx to 0x%016llx", 762 | sym_addr + addend, min_value, max_value); 763 | return false; 764 | } 765 | } 766 | break; 767 | case R_AARCH64_ABS16: 768 | { 769 | const ElfW(Addr) min_value = static_cast(INT16_MIN); 770 | const ElfW(Addr) max_value = static_cast(UINT16_MAX); 771 | if ((min_value <= (sym_addr + addend)) && 772 | ((sym_addr + addend) <= max_value)) { 773 | *reinterpret_cast(reloc) = (sym_addr + addend); 774 | } else { 775 | LOGE("0x%016llx out of range 0x%016llx to 0x%016llx", 776 | sym_addr + addend, min_value, max_value); 777 | return false; 778 | } 779 | } 780 | break; 781 | case R_AARCH64_PREL64: 782 | *reinterpret_cast(reloc) = sym_addr + addend - rel->r_offset; 783 | break; 784 | case R_AARCH64_PREL32: 785 | { 786 | const ElfW(Addr) min_value = static_cast(INT32_MIN); 787 | const ElfW(Addr) max_value = static_cast(UINT32_MAX); 788 | if ((min_value <= (sym_addr + addend - rel->r_offset)) && 789 | ((sym_addr + addend - rel->r_offset) <= max_value)) { 790 | *reinterpret_cast(reloc) = sym_addr + addend - rel->r_offset; 791 | } else { 792 | LOGE("0x%016llx out of range 0x%016llx to 0x%016llx", 793 | sym_addr + addend - rel->r_offset, min_value, max_value); 794 | return false; 795 | } 796 | } 797 | break; 798 | case R_AARCH64_PREL16: 799 | { 800 | const ElfW(Addr) min_value = static_cast(INT16_MIN); 801 | const ElfW(Addr) max_value = static_cast(UINT16_MAX); 802 | if ((min_value <= (sym_addr + addend - rel->r_offset)) && 803 | ((sym_addr + addend - rel->r_offset) <= max_value)) { 804 | *reinterpret_cast(reloc) = sym_addr + addend - rel->r_offset; 805 | } else { 806 | LOGE("0x%016llx out of range 0x%016llx to 0x%016llx", 807 | sym_addr + addend - rel->r_offset, min_value, max_value); 808 | return false; 809 | } 810 | } 811 | break; 812 | 813 | case R_AARCH64_COPY: 814 | LOGE("%s R_AARCH64_COPY relocations are not supported", get_realpath()); 815 | return false; 816 | case R_AARCH64_TLS_TPREL64: 817 | LOGD("RELO TLS_TPREL64 *** %16llx <- %16llx - %16llx\n", 818 | reloc, (sym_addr + addend), rel->r_offset); 819 | break; 820 | case R_AARCH64_TLS_DTPREL32: 821 | LOGD("RELO TLS_DTPREL32 *** %16llx <- %16llx - %16llx\n", 822 | reloc, (sym_addr + addend), rel->r_offset); 823 | break; 824 | #endif 825 | default: 826 | LOGE("unknown reloc type %d @ %p (%zu) sym_name: %s", type, rel, idx, sym_name); 827 | return false; 828 | } 829 | // */ 830 | } 831 | return true; 832 | } 833 | 834 | void soinfo::call_constructors() { 835 | // 對於so文件來說, 由於沒有_start函數 836 | // 因此init_func_和init_array_都無法傳參, 只能是默認值 837 | 838 | if(init_func_) { 839 | LOGD("init func: %p", init_func_); 840 | init_func_(0, nullptr, nullptr); 841 | } 842 | if(init_array_) { 843 | for(int i = 0; i < init_array_count_; i++) { 844 | if(!init_array_[i])continue; 845 | init_array_[i](0, nullptr, nullptr); 846 | } 847 | } 848 | 849 | } 850 | 851 | bool soinfo::protect_relro() { 852 | if (Utils::phdr_table_set_gnu_relro_prot(phdr, phnum, load_bias, PROT_READ) < 0) { 853 | LOGE("can't enable GNU RELRO protection for \"%s\": %s", 854 | get_realpath(), strerror(errno)); 855 | return false; 856 | } 857 | return true; 858 | } 859 | 860 | 861 | 862 | bool MyLoader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) { 863 | bool res = false; 864 | 865 | name_ = name; 866 | fd_ = fd; 867 | file_offset_ = file_offset; 868 | file_size_ = file_size; 869 | 870 | if (ReadElfHeader() && 871 | ReadProgramHeaders()) { 872 | res = true; 873 | } 874 | 875 | 876 | return res; 877 | } 878 | 879 | bool MyLoader::ReadElfHeader() { 880 | return memcpy(&(header_),start_addr_,sizeof(header_)); 881 | } 882 | 883 | bool MyLoader::ReadProgramHeaders() { 884 | 885 | phdr_num_ = header_.e_phnum; 886 | 887 | size_t size = phdr_num_ * sizeof(ElfW(Phdr)); 888 | 889 | void* data = Utils::getMapData(fd_, file_offset_, header_.e_phoff, size); 890 | if(data == nullptr) { 891 | LOGE("ProgramHeader mmap failed"); 892 | return false; 893 | } 894 | phdr_table_ = static_cast(data); 895 | 896 | return true; 897 | } 898 | 899 | 900 | bool MyLoader::Load() { 901 | bool res = false; 902 | if (ReserveAddressSpace() && 903 | LoadSegments() && 904 | FindPhdr()) { 905 | 906 | LOGD("Load Done........."); 907 | res = true; 908 | } 909 | 910 | // 獲取當前so (加載器的so) 911 | si_ = Utils::get_soinfo("libnglinker.so"); 912 | 913 | if(!si_) { 914 | LOGE("si_ return nullptr"); 915 | return false; 916 | } 917 | LOGD("si_ -> base: %lx", si_->base); 918 | 919 | // 使si_可以被修改 920 | mprotect((void*) PAGE_START(reinterpret_cast(si_)), 0x1000, PROT_READ | PROT_WRITE); 921 | 922 | // 修正so 923 | si_->base = load_start(); 924 | si_->size = load_size(); 925 | // si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller()); 926 | si_->load_bias = load_bias(); 927 | si_->phnum = phdr_count(); 928 | si_->phdr = loaded_phdr(); 929 | 930 | return res; 931 | } 932 | 933 | bool MyLoader::ReserveAddressSpace() { 934 | ElfW(Addr) min_vaddr; 935 | load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr); 936 | LOGD("load_size_: %x", load_size_); 937 | if (load_size_ == 0) { 938 | LOGE("\"%s\" has no loadable segments", name_.c_str()); 939 | return false; 940 | } 941 | 942 | uint8_t* addr = reinterpret_cast(min_vaddr); 943 | 944 | void* start; 945 | 946 | // Assume position independent executable by default. 947 | void* mmap_hint = nullptr; 948 | 949 | start = mmap(mmap_hint, load_size_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 950 | 951 | load_start_ = start; 952 | load_bias_ = reinterpret_cast(start) - addr; 953 | 954 | return true; 955 | } 956 | 957 | size_t MyLoader::phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count, 958 | ElfW(Addr)* out_min_vaddr) { 959 | ElfW(Addr) min_vaddr = UINTPTR_MAX; 960 | ElfW(Addr) max_vaddr = 0; 961 | 962 | bool found_pt_load = false; 963 | for (size_t i = 0; i < phdr_count; ++i) { 964 | const ElfW(Phdr)* phdr = &phdr_table[i]; 965 | 966 | if (phdr->p_type != PT_LOAD) { 967 | continue; 968 | } 969 | found_pt_load = true; 970 | 971 | if (phdr->p_vaddr < min_vaddr) { 972 | min_vaddr = phdr->p_vaddr; 973 | } 974 | 975 | if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) { 976 | max_vaddr = phdr->p_vaddr + phdr->p_memsz; 977 | } 978 | } 979 | if (!found_pt_load) { 980 | min_vaddr = 0; 981 | } 982 | 983 | min_vaddr = PAGE_START(min_vaddr); 984 | max_vaddr = PAGE_END(max_vaddr); 985 | 986 | if (out_min_vaddr != nullptr) { 987 | *out_min_vaddr = min_vaddr; 988 | } 989 | 990 | return max_vaddr - min_vaddr; 991 | } 992 | 993 | bool MyLoader::LoadSegments() { 994 | // 在這個函數中會往 ReserveAddressSpace 995 | // 裡mmap的那片內存填充數據 996 | 997 | 998 | for (size_t i = 0; i < phdr_num_; ++i) { 999 | const ElfW(Phdr)* phdr = &phdr_table_[i]; 1000 | 1001 | if (phdr->p_type != PT_LOAD) { 1002 | continue; 1003 | } 1004 | 1005 | // Segment addresses in memory. 1006 | ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_; 1007 | ElfW(Addr) seg_end = seg_start + phdr->p_memsz; 1008 | 1009 | ElfW(Addr) seg_page_start = PAGE_START(seg_start); 1010 | ElfW(Addr) seg_page_end = PAGE_END(seg_end); 1011 | 1012 | ElfW(Addr) seg_file_end = seg_start + phdr->p_filesz; 1013 | 1014 | // File offsets. 1015 | ElfW(Addr) file_start = phdr->p_offset; 1016 | ElfW(Addr) file_end = file_start + phdr->p_filesz; 1017 | 1018 | ElfW(Addr) file_page_start = PAGE_START(file_start); 1019 | ElfW(Addr) file_length = file_end - file_page_start; 1020 | 1021 | if (file_size_ <= 0) { 1022 | LOGE("\"%s\" invalid file size: %", name_.c_str(), file_size_); 1023 | return false; 1024 | } 1025 | 1026 | if (file_end > static_cast(file_size_)) { 1027 | LOGE("invalid ELF file"); 1028 | return false; 1029 | } 1030 | 1031 | if (file_length != 0) { 1032 | // 按AOSP裡那樣用mmap會有問題, 因此改為直接 memcpy 1033 | mprotect(reinterpret_cast(seg_page_start), seg_page_end - seg_page_start, PROT_WRITE); 1034 | void* c = (char*)start_addr_ + file_page_start; 1035 | void* res = memcpy(reinterpret_cast(seg_page_start), c, file_length); 1036 | 1037 | LOGD("[LoadSeg] %s seg_page_start: %lx c : %lx", strerror(errno), seg_page_start, c); 1038 | 1039 | } 1040 | 1041 | // if the segment is writable, and does not end on a page boundary, 1042 | // zero-fill it until the page limit. 1043 | if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) { 1044 | memset(reinterpret_cast(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end)); 1045 | } 1046 | 1047 | seg_file_end = PAGE_END(seg_file_end); 1048 | 1049 | // seg_file_end is now the first page address after the file 1050 | // content. If seg_end is larger, we need to zero anything 1051 | // between them. This is done by using a private anonymous 1052 | // map for all extra pages. 1053 | 1054 | if (seg_page_end > seg_file_end) { 1055 | size_t zeromap_size = seg_page_end - seg_file_end; 1056 | void* zeromap = mmap(reinterpret_cast(seg_file_end), 1057 | zeromap_size, 1058 | PFLAGS_TO_PROT(phdr->p_flags), 1059 | MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 1060 | -1, 1061 | 0); 1062 | if (zeromap == MAP_FAILED) { 1063 | LOGE("couldn't zero fill \"%s\" gap: %s", name_.c_str(), strerror(errno)); 1064 | return false; 1065 | } 1066 | 1067 | // 分配.bss節 1068 | prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss"); 1069 | } 1070 | } 1071 | 1072 | 1073 | return true; 1074 | } 1075 | 1076 | bool MyLoader::FindPhdr() { 1077 | 1078 | const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_; 1079 | 1080 | // If there is a PT_PHDR, use it directly. 1081 | for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { 1082 | if (phdr->p_type == PT_PHDR) { 1083 | return CheckPhdr(load_bias_ + phdr->p_vaddr); 1084 | } 1085 | } 1086 | 1087 | // Otherwise, check the first loadable segment. If its file offset 1088 | // is 0, it starts with the ELF header, and we can trivially find the 1089 | // loaded program header from it. 1090 | for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { 1091 | if (phdr->p_type == PT_LOAD) { 1092 | if (phdr->p_offset == 0) { 1093 | ElfW(Addr) elf_addr = load_bias_ + phdr->p_vaddr; 1094 | const ElfW(Ehdr)* ehdr = reinterpret_cast(elf_addr); 1095 | ElfW(Addr) offset = ehdr->e_phoff; 1096 | return CheckPhdr(reinterpret_cast(ehdr) + offset); 1097 | } 1098 | break; 1099 | } 1100 | } 1101 | 1102 | LOGE("can't find loaded phdr for \"%s\"", name_.c_str()); 1103 | return false; 1104 | } 1105 | 1106 | bool MyLoader::CheckPhdr(ElfW(Addr) loaded) { 1107 | const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_; 1108 | ElfW(Addr) loaded_end = loaded + (phdr_num_ * sizeof(ElfW(Phdr))); 1109 | for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) { 1110 | if (phdr->p_type != PT_LOAD) { 1111 | continue; 1112 | } 1113 | ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_; 1114 | ElfW(Addr) seg_end = phdr->p_filesz + seg_start; 1115 | if (seg_start <= loaded && loaded_end <= seg_end) { 1116 | loaded_phdr_ = reinterpret_cast(loaded); 1117 | return true; 1118 | } 1119 | } 1120 | LOGE("\"%s\" loaded phdr %p not in loadable segment", 1121 | name_.c_str(), reinterpret_cast(loaded)); 1122 | return false; 1123 | } 1124 | 1125 | const char* MyLoader::get_string(ElfW(Word) index) const { 1126 | return strtab_ + index; 1127 | } 1128 | 1129 | 1130 | 1131 | void MyLoader::run(const char* path) { 1132 | int fd; 1133 | struct stat sb; 1134 | fd = open(path, O_RDONLY); 1135 | fstat(fd, &sb); 1136 | start_addr_ = static_cast(mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)); 1137 | 1138 | 1139 | // 1. 讀取so文件 1140 | if(!Read(path, fd, 0, sb.st_size)){ 1141 | LOGD("Read so failed"); 1142 | munmap(start_addr_, sb.st_size); 1143 | close(fd); 1144 | } 1145 | 1146 | 1147 | // 2. 載入so 1148 | if(!Load()) { 1149 | LOGD("Load so failed"); 1150 | munmap(start_addr_, sb.st_size); 1151 | close(fd); 1152 | } 1153 | 1154 | // 使被加載的so有執行權限, 否則在調用.init_array時會報錯 1155 | mprotect(reinterpret_cast(load_bias_), sb.st_size, PROT_READ | PROT_WRITE | PROT_EXEC); 1156 | 1157 | 1158 | // 3. 預鏈接, 主要處理 .dynamic節 1159 | si_->prelink_image(); 1160 | 1161 | 1162 | // 4. 正式鏈接, 在這裡處理重定位的信息 1163 | si_->link_image(); 1164 | 1165 | // 5. 調用.init和.init_array 1166 | si_->call_constructors(); 1167 | 1168 | close(fd); 1169 | } 1170 | 1171 | 1172 | 1173 | 1174 | -------------------------------------------------------------------------------- /app/src/main/cpp/MyLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by user on 2024/6/15. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "log.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "soinfo.h" 18 | 19 | //#define __LP64__ 1 20 | #define PROT_READ 0x1 21 | #define MAP_PRIVATE 0x02 22 | #define PR_SET_VMA 0x53564d41 23 | #define PR_SET_VMA_ANON_NAME 0 24 | #define FLAG_LINKER 0x00000010 // The linker itself 25 | #define FLAG_GNU_HASH 0x00000040 // uses gnu hash 26 | #define SUPPORTED_DT_FLAGS_1 (DF_1_NOW | DF_1_GLOBAL | DF_1_NODELETE | DF_1_PIE) 27 | #define R_GENERIC_NONE 0 28 | #define R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT 29 | #define R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT 30 | #define R_GENERIC_RELATIVE R_AARCH64_RELATIVE 31 | #define R_GENERIC_IRELATIVE R_AARCH64_IRELATIVE 32 | #define R_AARCH64_TLS_TPREL64 1030 33 | #define R_AARCH64_TLS_DTPREL32 1031 34 | 35 | 36 | 37 | #define PAGE_START(x) ((x) & PAGE_MASK) 38 | #define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE-1)) 39 | #define PAGE_OFFSET(x) ((x) & ~PAGE_MASK) 40 | #define MAYBE_MAP_FLAG(x, from, to) (((x) & (from)) ? (to) : 0) 41 | #define PFLAGS_TO_PROT(x) (MAYBE_MAP_FLAG((x), PF_X, PROT_EXEC) | \ 42 | MAYBE_MAP_FLAG((x), PF_R, PROT_READ) | \ 43 | MAYBE_MAP_FLAG((x), PF_W, PROT_WRITE)) 44 | #define powerof2(x) ((((x)-1)&(x))==0) 45 | #if defined(__LP64__) 46 | #define ELFW(what) ELF64_ ## what 47 | #else 48 | #define ELFW(what) ELF32_ ## what 49 | #endif 50 | 51 | // Android uses RELA for LP64. 52 | // from: https://blog.xhyeax.com/2022/06/08/android-arm64-got-hook-rela-plt/ 53 | //#if defined(__LP64__) 54 | //#define USE_RELA 1 55 | //#endif 56 | 57 | 58 | class soinfo; 59 | 60 | constexpr off64_t kPageMask = ~static_cast(PAGE_SIZE-1); 61 | typedef void (*linker_ctor_function_t)(int, char**, char**); 62 | typedef void (*linker_dtor_function_t)(); 63 | 64 | 65 | class plain_reloc_iterator { 66 | #if defined(USE_RELA) 67 | typedef ElfW(Rela) rel_t; 68 | #else 69 | typedef ElfW(Rel) rel_t; 70 | #endif 71 | public: 72 | plain_reloc_iterator(rel_t* rel_array, size_t count) 73 | : begin_(rel_array), end_(begin_ + count), current_(begin_) {} 74 | 75 | bool has_next() { 76 | return current_ < end_; 77 | } 78 | 79 | rel_t* next() { 80 | return current_++; 81 | } 82 | private: 83 | rel_t* const begin_; 84 | rel_t* const end_; 85 | rel_t* current_; 86 | 87 | }; 88 | 89 | 90 | class sleb128_decoder { 91 | public: 92 | sleb128_decoder(const uint8_t* buffer, size_t count) 93 | : current_(buffer), end_(buffer+count) { } 94 | 95 | size_t pop_front() { 96 | size_t value = 0; 97 | static const size_t size = CHAR_BIT * sizeof(value); 98 | 99 | size_t shift = 0; 100 | uint8_t byte; 101 | 102 | do { 103 | if (current_ >= end_) { 104 | LOGE("sleb128_decoder ran out of bounds"); 105 | } 106 | byte = *current_++; 107 | value |= (static_cast(byte & 127) << shift); 108 | shift += 7; 109 | } while (byte & 128); 110 | 111 | if (shift < size && (byte & 64)) { 112 | value |= -(static_cast(1) << shift); 113 | } 114 | 115 | return value; 116 | } 117 | 118 | private: 119 | const uint8_t* current_; 120 | const uint8_t* const end_; 121 | }; 122 | 123 | 124 | const size_t RELOCATION_GROUPED_BY_INFO_FLAG = 1; 125 | const size_t RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG = 2; 126 | const size_t RELOCATION_GROUPED_BY_ADDEND_FLAG = 4; 127 | const size_t RELOCATION_GROUP_HAS_ADDEND_FLAG = 8; 128 | template 129 | class packed_reloc_iterator { 130 | #if defined(USE_RELA) 131 | typedef ElfW(Rela) rel_t; 132 | #else 133 | typedef ElfW(Rel) rel_t; 134 | #endif 135 | public: 136 | explicit packed_reloc_iterator(decoder_t&& decoder) 137 | : decoder_(decoder) { 138 | // initialize fields 139 | memset(&reloc_, 0, sizeof(reloc_)); 140 | relocation_count_ = decoder_.pop_front(); 141 | reloc_.r_offset = decoder_.pop_front(); 142 | relocation_index_ = 0; 143 | relocation_group_index_ = 0; 144 | group_size_ = 0; 145 | } 146 | 147 | bool has_next() const { 148 | return relocation_index_ < relocation_count_; 149 | } 150 | 151 | rel_t* next() { 152 | if (relocation_group_index_ == group_size_) { 153 | if (!read_group_fields()) { 154 | // Iterator is inconsistent state; it should not be called again 155 | // but in case it is let's make sure has_next() returns false. 156 | relocation_index_ = relocation_count_ = 0; 157 | return nullptr; 158 | } 159 | } 160 | 161 | if (is_relocation_grouped_by_offset_delta()) { 162 | reloc_.r_offset += group_r_offset_delta_; 163 | } else { 164 | reloc_.r_offset += decoder_.pop_front(); 165 | } 166 | 167 | if (!is_relocation_grouped_by_info()) { 168 | reloc_.r_info = decoder_.pop_front(); 169 | } 170 | 171 | #if defined(USE_RELA) 172 | if (is_relocation_group_has_addend() && 173 | !is_relocation_grouped_by_addend()) { 174 | reloc_.r_addend += decoder_.pop_front(); 175 | } 176 | #endif 177 | 178 | relocation_index_++; 179 | relocation_group_index_++; 180 | 181 | return &reloc_; 182 | } 183 | private: 184 | bool read_group_fields() { 185 | group_size_ = decoder_.pop_front(); 186 | group_flags_ = decoder_.pop_front(); 187 | 188 | if (is_relocation_grouped_by_offset_delta()) { 189 | group_r_offset_delta_ = decoder_.pop_front(); 190 | } 191 | 192 | if (is_relocation_grouped_by_info()) { 193 | reloc_.r_info = decoder_.pop_front(); 194 | } 195 | 196 | if (is_relocation_group_has_addend() && 197 | is_relocation_grouped_by_addend()) { 198 | #if !defined(USE_RELA) 199 | // This platform does not support rela, and yet we have it encoded in android_rel section. 200 | LOGE("unexpected r_addend in android.rel section"); 201 | return false; 202 | #else 203 | reloc_.r_addend += decoder_.pop_front(); 204 | } else if (!is_relocation_group_has_addend()) { 205 | reloc_.r_addend = 0; 206 | #endif 207 | } 208 | 209 | relocation_group_index_ = 0; 210 | return true; 211 | } 212 | 213 | bool is_relocation_grouped_by_info() { 214 | return (group_flags_ & RELOCATION_GROUPED_BY_INFO_FLAG) != 0; 215 | } 216 | 217 | bool is_relocation_grouped_by_offset_delta() { 218 | return (group_flags_ & RELOCATION_GROUPED_BY_OFFSET_DELTA_FLAG) != 0; 219 | } 220 | 221 | bool is_relocation_grouped_by_addend() { 222 | return (group_flags_ & RELOCATION_GROUPED_BY_ADDEND_FLAG) != 0; 223 | } 224 | 225 | bool is_relocation_group_has_addend() { 226 | return (group_flags_ & RELOCATION_GROUP_HAS_ADDEND_FLAG) != 0; 227 | } 228 | 229 | decoder_t decoder_; 230 | size_t relocation_count_; 231 | size_t group_size_; 232 | size_t group_flags_; 233 | size_t group_r_offset_delta_; 234 | size_t relocation_index_; 235 | size_t relocation_group_index_; 236 | rel_t reloc_; 237 | }; 238 | 239 | 240 | class Utils { 241 | public: 242 | static size_t page_offset(off64_t offset) ; 243 | 244 | static off64_t page_start(off64_t offset) ; 245 | 246 | static bool safe_add(off64_t* out, off64_t a, size_t b); 247 | 248 | static void* getMapData(int fd, off64_t base_offset, size_t elf_offset, size_t size); 249 | 250 | static void phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, size_t phdr_count, 251 | ElfW(Addr) load_bias, ElfW(Dyn)** dynamic, 252 | ElfW(Word)* dynamic_flags) ; 253 | 254 | static soinfo* get_soinfo(const char* so_name); 255 | 256 | 257 | static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr); 258 | 259 | static ElfW(Addr) get_addend(ElfW(Rela)* rela, ElfW(Addr) reloc_addr __unused); 260 | 261 | static ElfW(Addr) get_export_func(char* path, char* func_name); 262 | 263 | static int phdr_table_set_gnu_relro_prot(const ElfW(Phdr)* phdr_table, size_t phdr_count, 264 | ElfW(Addr) load_bias, int prot_flags); 265 | }; 266 | 267 | 268 | 269 | 270 | class MyLoader { 271 | private: 272 | int fd_; 273 | off64_t file_offset_; 274 | off64_t file_size_; 275 | ElfW(Ehdr) header_; 276 | size_t phdr_num_; 277 | const ElfW(Phdr)* phdr_table_; 278 | size_t shdr_num_; 279 | const ElfW(Shdr)* shdr_table_; 280 | const ElfW(Dyn)* dynamic_; 281 | const char* strtab_; 282 | size_t strtab_size_; 283 | std::string name_; 284 | void* load_start_; 285 | size_t load_size_; 286 | ElfW(Addr) load_bias_; 287 | void* start_addr_; 288 | const ElfW(Phdr)* loaded_phdr_; 289 | soinfo* si_; 290 | 291 | public: 292 | MyLoader(): fd_(-1), file_offset_(0), file_size_(0), phdr_num_(0), 293 | phdr_table_(nullptr), shdr_table_(nullptr), shdr_num_(0), dynamic_(nullptr), strtab_(nullptr), 294 | strtab_size_(0), load_start_(nullptr), load_size_(0) { 295 | } 296 | size_t phdr_count() const { return phdr_num_; } 297 | ElfW(Addr) load_start() const { return reinterpret_cast(load_start_); } 298 | size_t load_size() const { return load_size_; } 299 | ElfW(Addr) load_bias() const { return load_bias_; } 300 | const ElfW(Phdr)* loaded_phdr() const { return loaded_phdr_; } 301 | 302 | public: 303 | 304 | bool Read(const char* name, int fd, off64_t file_offset, off64_t file_size); 305 | 306 | bool ReadElfHeader(); 307 | 308 | bool ReadProgramHeaders(); 309 | 310 | 311 | bool Load(); 312 | 313 | bool ReserveAddressSpace(); 314 | 315 | size_t phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count, 316 | ElfW(Addr)* out_min_vaddr); 317 | 318 | bool LoadSegments(); 319 | 320 | bool FindPhdr(); 321 | 322 | bool CheckPhdr(ElfW(Addr) loaded); 323 | 324 | const char* get_string(ElfW(Word) index) const; 325 | 326 | void run(const char* path); 327 | 328 | 329 | }; 330 | 331 | 332 | -------------------------------------------------------------------------------- /app/src/main/cpp/log.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | 5 | #define TAG "nglog" 6 | 7 | // 定義info信息 8 | 9 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__) 10 | 11 | // 定義debug信息 12 | 13 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) 14 | 15 | // 定義error信息 16 | 17 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "MyLoader.h" 4 | 5 | 6 | extern "C" JNIEXPORT jstring 7 | 8 | JNICALL 9 | Java_ng1ok_linker_MainActivity_stringFromJNI( 10 | JNIEnv *env, 11 | jobject /* this */) { 12 | std::string hello = "Hello from C++"; 13 | return env->NewStringUTF(hello.c_str()); 14 | } 15 | 16 | 17 | extern "C" 18 | JNIEXPORT void JNICALL 19 | Java_ng1ok_linker_MainActivity_test(JNIEnv *env, jobject thiz) { 20 | MyLoader myLoader; 21 | myLoader.run("/data/local/tmp/libdemo1.so"); 22 | 23 | LOGD("test done...."); 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/cpp/soinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #if defined(__aarch64__) || defined(__x86_64__) 8 | #define USE_RELA 1 9 | #endif 10 | 11 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 12 | TypeName(const TypeName&) = delete; \ 13 | void operator=(const TypeName&) = delete 14 | 15 | #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ 16 | TypeName() = delete; \ 17 | DISALLOW_COPY_AND_ASSIGN(TypeName) 18 | 19 | typedef void (*linker_dtor_function_t)(); 20 | typedef void (*linker_ctor_function_t)(int, char**, char**); 21 | 22 | 23 | template 24 | struct LinkedListEntry { 25 | LinkedListEntry* next; 26 | T* element; 27 | }; 28 | 29 | // ForwardInputIterator 30 | template 31 | class LinkedListIterator { 32 | public: 33 | LinkedListIterator() : entry_(nullptr) {} 34 | LinkedListIterator(const LinkedListIterator& that) : entry_(that.entry_) {} 35 | explicit LinkedListIterator(LinkedListEntry* entry) : entry_(entry) {} 36 | 37 | LinkedListIterator& operator=(const LinkedListIterator& that) { 38 | entry_ = that.entry_; 39 | return *this; 40 | } 41 | 42 | LinkedListIterator& operator++() { 43 | entry_ = entry_->next; 44 | return *this; 45 | } 46 | 47 | T* const operator*() { 48 | return entry_->element; 49 | } 50 | 51 | bool operator==(const LinkedListIterator& that) const { 52 | return entry_ == that.entry_; 53 | } 54 | 55 | bool operator!=(const LinkedListIterator& that) const { 56 | return entry_ != that.entry_; 57 | } 58 | 59 | private: 60 | LinkedListEntry *entry_; 61 | }; 62 | 63 | /* 64 | * Represents linked list of objects of type T 65 | */ 66 | template 67 | class LinkedList { 68 | public: 69 | typedef LinkedListIterator iterator; 70 | typedef T* value_type; 71 | 72 | LinkedList() : head_(nullptr), tail_(nullptr) {} 73 | ~LinkedList() { 74 | clear(); 75 | } 76 | 77 | LinkedList(LinkedList&& that) { 78 | this->head_ = that.head_; 79 | this->tail_ = that.tail_; 80 | that.head_ = that.tail_ = nullptr; 81 | } 82 | 83 | void push_front(T* const element) { 84 | LinkedListEntry* new_entry = Allocator::alloc(); 85 | new_entry->next = head_; 86 | new_entry->element = element; 87 | head_ = new_entry; 88 | if (tail_ == nullptr) { 89 | tail_ = new_entry; 90 | } 91 | } 92 | 93 | void push_back(T* const element) { 94 | LinkedListEntry* new_entry = Allocator::alloc(); 95 | new_entry->next = nullptr; 96 | new_entry->element = element; 97 | if (tail_ == nullptr) { 98 | tail_ = head_ = new_entry; 99 | } else { 100 | tail_->next = new_entry; 101 | tail_ = new_entry; 102 | } 103 | } 104 | 105 | T* pop_front() { 106 | if (head_ == nullptr) { 107 | return nullptr; 108 | } 109 | 110 | LinkedListEntry* entry = head_; 111 | T* element = entry->element; 112 | head_ = entry->next; 113 | Allocator::free(entry); 114 | 115 | if (head_ == nullptr) { 116 | tail_ = nullptr; 117 | } 118 | 119 | return element; 120 | } 121 | 122 | T* front() const { 123 | if (head_ == nullptr) { 124 | return nullptr; 125 | } 126 | 127 | return head_->element; 128 | } 129 | 130 | void clear() { 131 | while (head_ != nullptr) { 132 | LinkedListEntry* p = head_; 133 | head_ = head_->next; 134 | Allocator::free(p); 135 | } 136 | 137 | tail_ = nullptr; 138 | } 139 | 140 | bool empty() { 141 | return (head_ == nullptr); 142 | } 143 | 144 | template 145 | void for_each(F action) const { 146 | visit([&] (T* si) { 147 | action(si); 148 | return true; 149 | }); 150 | } 151 | 152 | template 153 | bool visit(F action) const { 154 | for (LinkedListEntry* e = head_; e != nullptr; e = e->next) { 155 | if (!action(e->element)) { 156 | return false; 157 | } 158 | } 159 | return true; 160 | } 161 | 162 | template 163 | void remove_if(F predicate) { 164 | for (LinkedListEntry* e = head_, *p = nullptr; e != nullptr;) { 165 | if (predicate(e->element)) { 166 | LinkedListEntry* next = e->next; 167 | if (p == nullptr) { 168 | head_ = next; 169 | } else { 170 | p->next = next; 171 | } 172 | 173 | if (tail_ == e) { 174 | tail_ = p; 175 | } 176 | 177 | Allocator::free(e); 178 | 179 | e = next; 180 | } else { 181 | p = e; 182 | e = e->next; 183 | } 184 | } 185 | } 186 | 187 | void remove(T* element) { 188 | remove_if([&](T* e) { 189 | return e == element; 190 | }); 191 | } 192 | 193 | template 194 | T* find_if(F predicate) const { 195 | for (LinkedListEntry* e = head_; e != nullptr; e = e->next) { 196 | if (predicate(e->element)) { 197 | return e->element; 198 | } 199 | } 200 | 201 | return nullptr; 202 | } 203 | 204 | iterator begin() const { 205 | return iterator(head_); 206 | } 207 | 208 | iterator end() const { 209 | return iterator(nullptr); 210 | } 211 | 212 | iterator find(T* value) const { 213 | for (LinkedListEntry* e = head_; e != nullptr; e = e->next) { 214 | if (e->element == value) { 215 | return iterator(e); 216 | } 217 | } 218 | 219 | return end(); 220 | } 221 | 222 | size_t copy_to_array(T* array[], size_t array_length) const { 223 | size_t sz = 0; 224 | for (LinkedListEntry* e = head_; sz < array_length && e != nullptr; e = e->next) { 225 | array[sz++] = e->element; 226 | } 227 | 228 | return sz; 229 | } 230 | 231 | bool contains(const T* el) const { 232 | for (LinkedListEntry* e = head_; e != nullptr; e = e->next) { 233 | if (e->element == el) { 234 | return true; 235 | } 236 | } 237 | return false; 238 | } 239 | 240 | static LinkedList make_list(T* const element) { 241 | LinkedList one_element_list; 242 | one_element_list.push_back(element); 243 | return one_element_list; 244 | } 245 | 246 | private: 247 | LinkedListEntry* head_; 248 | LinkedListEntry* tail_; 249 | DISALLOW_COPY_AND_ASSIGN(LinkedList); 250 | }; 251 | 252 | 253 | struct soinfo; 254 | 255 | class SoinfoListAllocator { 256 | public: 257 | static LinkedListEntry* alloc(); 258 | static void free(LinkedListEntry* entry); 259 | 260 | private: 261 | // unconstructable 262 | DISALLOW_IMPLICIT_CONSTRUCTORS(SoinfoListAllocator); 263 | }; 264 | 265 | class NamespaceListAllocator { 266 | public: 267 | static LinkedListEntry* alloc(); 268 | static void free(LinkedListEntry* entry); 269 | 270 | private: 271 | // unconstructable 272 | DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceListAllocator); 273 | }; 274 | 275 | typedef LinkedList soinfo_list_t; 276 | typedef LinkedList android_namespace_list_t; 277 | 278 | 279 | class SymbolName { 280 | public: 281 | explicit SymbolName(const char* name) 282 | : name_(name), has_elf_hash_(false), has_gnu_hash_(false), 283 | elf_hash_(0), gnu_hash_(0) { } 284 | 285 | const char* get_name() { 286 | return name_; 287 | } 288 | 289 | uint32_t elf_hash(); 290 | uint32_t gnu_hash(); 291 | 292 | private: 293 | const char* name_; 294 | bool has_elf_hash_; 295 | bool has_gnu_hash_; 296 | uint32_t elf_hash_; 297 | uint32_t gnu_hash_; 298 | 299 | DISALLOW_IMPLICIT_CONSTRUCTORS(SymbolName); 300 | }; 301 | 302 | 303 | struct soinfo { 304 | #if defined(__work_around_b_24465209__) 305 | private: 306 | char old_name_[SOINFO_NAME_LEN]; 307 | #endif 308 | public: 309 | const ElfW(Phdr)* phdr; 310 | size_t phnum; 311 | #if defined(__work_around_b_24465209__) 312 | ElfW(Addr) unused0; // DO NOT USE, maintained for compatibility. 313 | #endif 314 | ElfW(Addr) base; 315 | size_t size; 316 | 317 | #if defined(__work_around_b_24465209__) 318 | uint32_t unused1; // DO NOT USE, maintained for compatibility. 319 | #endif 320 | 321 | ElfW(Dyn)* dynamic; 322 | 323 | #if defined(__work_around_b_24465209__) 324 | uint32_t unused2; // DO NOT USE, maintained for compatibility 325 | uint32_t unused3; // DO NOT USE, maintained for compatibility 326 | #endif 327 | 328 | soinfo* next; 329 | public: 330 | uint32_t flags_; 331 | 332 | const char* strtab_; 333 | ElfW(Sym)* symtab_; 334 | 335 | size_t nbucket_; 336 | size_t nchain_; 337 | uint32_t* bucket_; 338 | uint32_t* chain_; 339 | 340 | #if defined(__mips__) || !defined(__LP64__) 341 | // This is only used by mips and mips64, but needs to be here for 342 | // all 32-bit architectures to preserve binary compatibility. 343 | ElfW(Addr)** plt_got_; 344 | #endif 345 | 346 | #if defined(USE_RELA) 347 | ElfW(Rela)* plt_rela_; 348 | size_t plt_rela_count_; 349 | 350 | ElfW(Rela)* rela_; 351 | size_t rela_count_; 352 | #else 353 | ElfW(Rel)* plt_rel_; 354 | size_t plt_rel_count_; 355 | 356 | ElfW(Rel)* rel_; 357 | size_t rel_count_; 358 | #endif 359 | 360 | linker_ctor_function_t* preinit_array_; 361 | size_t preinit_array_count_; 362 | 363 | linker_ctor_function_t* init_array_; 364 | size_t init_array_count_; 365 | linker_dtor_function_t* fini_array_; 366 | size_t fini_array_count_; 367 | 368 | linker_ctor_function_t init_func_; 369 | linker_dtor_function_t fini_func_; 370 | 371 | #if defined(__arm__) 372 | public: 373 | // ARM EABI section used for stack unwinding. 374 | uint32_t* ARM_exidx; 375 | size_t ARM_exidx_count; 376 | private: 377 | #elif defined(__mips__) 378 | uint32_t mips_symtabno_; 379 | uint32_t mips_local_gotno_; 380 | uint32_t mips_gotsym_; 381 | bool mips_relocate_got(const VersionTracker& version_tracker, 382 | const soinfo_list_t& global_group, 383 | const soinfo_list_t& local_group); 384 | #if !defined(__LP64__) 385 | bool mips_check_and_adjust_fp_modes(); 386 | #endif 387 | #endif 388 | size_t ref_count_; 389 | public: 390 | link_map link_map_head; 391 | 392 | bool constructors_called; 393 | 394 | // When you read a virtual address from the ELF file, add this 395 | // value to get the corresponding address in the process' address space. 396 | ElfW(Addr) load_bias; 397 | 398 | #if !defined(__LP64__) 399 | bool has_text_relocations; 400 | #endif 401 | bool has_DT_SYMBOLIC; 402 | 403 | 404 | bool inline has_min_version(uint32_t min_version __unused) const { 405 | #if defined(__work_around_b_24465209__) 406 | return (flags_ & FLAG_NEW_SOINFO) != 0 && version_ >= min_version; 407 | #else 408 | return true; 409 | #endif 410 | } 411 | 412 | bool is_linked() const; 413 | bool is_linker() const; 414 | bool is_main_executable() const; 415 | 416 | void set_linked(); 417 | void set_linker_flag(); 418 | void set_main_executable(); 419 | void set_nodelete(); 420 | 421 | void increment_ref_count(); 422 | size_t decrement_ref_count(); 423 | 424 | soinfo* get_local_group_root() const; 425 | 426 | void set_soname(const char* soname); 427 | const char* get_soname() const; 428 | const char* get_realpath() const; 429 | const ElfW(Versym)* get_versym(size_t n) const; 430 | ElfW(Addr) get_verneed_ptr() const; 431 | size_t get_verneed_cnt() const; 432 | ElfW(Addr) get_verdef_ptr() const; 433 | size_t get_verdef_cnt() const; 434 | 435 | uint32_t get_target_sdk_version() const; 436 | 437 | void set_dt_runpath(const char *); 438 | const std::vector& get_dt_runpath() const; 439 | android_namespace_t* get_primary_namespace(); 440 | void add_secondary_namespace(android_namespace_t* secondary_ns); 441 | android_namespace_list_t& get_secondary_namespaces(); 442 | 443 | void set_mapped_by_caller(bool reserved_map); 444 | bool is_mapped_by_caller() const; 445 | 446 | uintptr_t get_handle() const; 447 | void generate_handle(); 448 | void* to_handle(); 449 | 450 | 451 | public: 452 | // This part of the structure is only available 453 | // when FLAG_NEW_SOINFO is set in this->flags. 454 | uint32_t version_; 455 | 456 | // version >= 0 457 | dev_t st_dev_; 458 | ino_t st_ino_; 459 | 460 | // dependency graph 461 | soinfo_list_t children_; 462 | soinfo_list_t parents_; 463 | 464 | // version >= 1 465 | off64_t file_offset_; 466 | uint32_t rtld_flags_; 467 | uint32_t dt_flags_1_; 468 | size_t strtab_size_; 469 | 470 | // version >= 2 471 | 472 | size_t gnu_nbucket_; 473 | uint32_t* gnu_bucket_; 474 | uint32_t* gnu_chain_; 475 | uint32_t gnu_maskwords_; 476 | uint32_t gnu_shift2_; 477 | ElfW(Addr)* gnu_bloom_filter_; 478 | 479 | soinfo* local_group_root_; 480 | 481 | uint8_t* android_relocs_; 482 | size_t android_relocs_size_; 483 | 484 | const char* soname_; 485 | std::string realpath_; 486 | 487 | const ElfW(Versym)* versym_; 488 | 489 | ElfW(Addr) verdef_ptr_; 490 | size_t verdef_cnt_; 491 | 492 | ElfW(Addr) verneed_ptr_; 493 | size_t verneed_cnt_; 494 | 495 | uint32_t target_sdk_version_; 496 | 497 | // version >= 3 498 | std::vector dt_runpath_; 499 | void* primary_namespace_; 500 | android_namespace_list_t secondary_namespaces_; 501 | uintptr_t handle_; 502 | 503 | friend soinfo* get_libdl_info(const char* linker_path, const link_map& linker_map); 504 | 505 | 506 | public: 507 | const char* get_string(ElfW(Word) index) const ; 508 | void set_dt_flags_1(uint32_t dt_flags_1) ; 509 | 510 | 511 | bool prelink_image(); 512 | void fortest() { 513 | LOGD("gnu_bloom_filter_ = %lx", gnu_bloom_filter_); 514 | }; 515 | 516 | bool link_image(); 517 | 518 | template 519 | 520 | bool relocate(ElfRelIteratorT&& rel_iterator); 521 | 522 | bool find_symbol_by_name(SymbolName& symbol_name, const ElfW(Sym)** symbol) const; 523 | 524 | bool is_gnu_hash() const; 525 | 526 | bool elf_lookup(SymbolName& symbol_name, uint32_t* symbol_index) const; 527 | 528 | bool gnu_lookup(SymbolName& symbol_name, uint32_t* symbol_index) const; 529 | 530 | ElfW(Addr) resolve_symbol_address(const ElfW(Sym)* s) const; 531 | 532 | void call_constructors(); 533 | 534 | bool protect_relro(); 535 | }; 536 | -------------------------------------------------------------------------------- /app/src/main/java/ng1ok/linker/MainActivity.java: -------------------------------------------------------------------------------- 1 | package ng1ok.linker; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.widget.TextView; 8 | 9 | 10 | import ng1ok.linker.databinding.ActivityMainBinding; 11 | 12 | public class MainActivity extends AppCompatActivity { 13 | 14 | // Used to load the 'linker' library on application startup. 15 | static { 16 | System.loadLibrary("nglinker"); 17 | // System.loadLibrary("demo1"); 18 | } 19 | 20 | private ActivityMainBinding binding; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 27 | setContentView(binding.getRoot()); 28 | 29 | // Example of a call to a native method 30 | TextView tv = binding.sampleText; 31 | tv.setText(stringFromJNI()); 32 | test(); 33 | demo1Func(); 34 | } 35 | public native String demo1Func(); 36 | /** 37 | * A native method that is implemented by the 'linker' native library, 38 | * which is packaged with this application. 39 | */ 40 | public native String stringFromJNI(); 41 | 42 | public native void test(); 43 | 44 | 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Ng1okLinker 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/ng1ok/linker/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ng1ok.linker; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.2.2' apply false 4 | id 'com.android.library' version '8.2.2' apply false 5 | } -------------------------------------------------------------------------------- /demo1/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /demo1/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | namespace 'ng1ok.demo1' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | minSdk 24 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles "consumer-rules.pro" 14 | externalNativeBuild { 15 | cmake { 16 | cppFlags "" 17 | } 18 | } 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | externalNativeBuild { 28 | cmake { 29 | path "src/main/cpp/CMakeLists.txt" 30 | version "3.22.1" 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | implementation 'androidx.appcompat:appcompat:1.7.0' 42 | implementation 'com.google.android.material:material:1.12.0' 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 46 | 47 | } -------------------------------------------------------------------------------- /demo1/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/demo1/consumer-rules.pro -------------------------------------------------------------------------------- /demo1/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /demo1/src/androidTest/java/ng1ok/demo1/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ng1ok.demo1; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("ng1ok.demo1.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /demo1/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /demo1/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, 9 | # Since this is the top level CMakeLists.txt, the project name is also accessible 10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level 11 | # build script scope). 12 | project("demo1") 13 | 14 | # Creates and names a library, sets it as either STATIC 15 | # or SHARED, and provides the relative paths to its source code. 16 | # You can define multiple libraries, and CMake builds them for you. 17 | # Gradle automatically packages shared libraries with your APK. 18 | # 19 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define 20 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} 21 | # is preferred for the same purpose. 22 | # 23 | # In order to load a library into your app from Java/Kotlin, you must call 24 | # System.loadLibrary() and pass the name of the library defined here; 25 | # for GameActivity/NativeActivity derived applications, the same library name must be 26 | # used in the AndroidManifest.xml file. 27 | add_library(${CMAKE_PROJECT_NAME} SHARED 28 | # List C/C++ source files with relative paths to this CMakeLists.txt. 29 | demo1.cpp) 30 | 31 | # Specifies libraries CMake should link to your target library. You 32 | # can link libraries from various origins, such as libraries defined in this 33 | # build script, prebuilt third-party libraries, or Android system libraries. 34 | target_link_libraries(${CMAKE_PROJECT_NAME} 35 | # List libraries link to the target library 36 | android 37 | log) -------------------------------------------------------------------------------- /demo1/src/main/cpp/demo1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define TAG "nglog" 6 | 7 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__) 8 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) 9 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) 10 | 11 | 12 | extern "C" JNIEXPORT jstring JNICALL 13 | Java_ng1ok_demo1_NativeLib_stringFromJNI( 14 | JNIEnv* env, 15 | jobject /* this */) { 16 | std::string hello = "Hello from C++"; 17 | return env->NewStringUTF(hello.c_str()); 18 | } 19 | 20 | extern "C" 21 | JNIEXPORT jstring JNICALL 22 | Java_ng1ok_linker_MainActivity_demo1Func(JNIEnv *env, jobject thiz) { 23 | LOGD("Java_ng1ok_linker_MainActivity_demo1Func calleeeeeeeddddddddd"); 24 | std::string str = "Java_ng1ok_linker_MainActivity_demo1Func"; 25 | 26 | return env->NewStringUTF(str.c_str()); 27 | } 28 | 29 | 30 | __attribute__((constructor())) 31 | void sayHello(){ 32 | LOGD("[from libdemo1.so .init_array] Hello~~~"); 33 | } 34 | 35 | 36 | extern "C" { 37 | void _init(void){ 38 | LOGD("[from libdemo1.so .init] _init~~~~"); 39 | } 40 | } -------------------------------------------------------------------------------- /demo1/src/main/java/ng1ok/demo1/NativeLib.java: -------------------------------------------------------------------------------- 1 | package ng1ok.demo1; 2 | 3 | public class NativeLib { 4 | 5 | // Used to load the 'demo1' library on application startup. 6 | static { 7 | System.loadLibrary("demo1"); 8 | } 9 | 10 | /** 11 | * A native method that is implemented by the 'demo1' native library, 12 | * which is packaged with this application. 13 | */ 14 | public native String stringFromJNI(); 15 | } -------------------------------------------------------------------------------- /demo1/src/test/java/ng1ok/demo1/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ng1ok.demo1; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngiokweng/ng1ok-linker/1e0a6a9e2f9d13a3c436eefce5b5f1e601b93f76/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jun 15 14:19:43 HKT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "Ng1okLinker" 17 | include ':app' 18 | include ':demo1' 19 | --------------------------------------------------------------------------------