├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
--------------------------------------------------------------------------------