安卓链接器为ARM程序集重新定位C函数,但不适用于x86



我成功地将Android项目的ARM库更改为PIC(位置无关代码),因为我想进行一些不相关的修复,而Android自Lollipop以来只支持PIC库。(我的叉子的最新来源是http://github.com/sleekweasel/Beebdroid)

现在,我也想在整理和编写pull请求之前让x86也工作起来(因为它在原始项目中),但我的x86比我的ARM知识要弱得多,安卓汇编文章似乎只处理ARM。

我遇到问题的两条指令是LEACALL:它们导致ld发出

warning: shared library text segment is not shareable

如果我把它们注释掉,那么./gradlew build会很高兴地链接,但很明显,代码工作得不太好。

以下是该项目的片段——也许它们会比我的英文描述更清晰。我认为这是完整的上下文,因为只有指令及其与链接器的交互才是问题所在:

app/src/main/jni/6502asm_x86.S:

.intel_syntax noprefix
.text
.global exec6502
.global acpu
exec6502:
pusha
# Keep CPU* in EBP
lea  ebp,acpu  // Causes PIC to fail.
// ... code removed ...
lea   ebx, fns_asm // Causes PIC to fail.
// ... code removed ...
call do_poll_C // Causes PIC to fail.
// ... code removed ...
popa
ret
// Lots of op-code implementations here - they have no effect on linking
// .section .rodata
.balign 4
fns_asm:
.long 0                 // 0x00 BRK
.long 0 - fns_asm + opasm_ora_indzx     // 0x01 ORA (,x)
.long 0 - fns_asm + opasm_undef

app/src/main/jni/main.h

typedef struct M6502_struct M6502;
struct M6502_struct { ... };
void exec6502(M6502* cpu);
extern void do_poll_C(M6502*, int c);

app/src/main/jni/6502.c

M6502 acpu;
M6502* the_cpu = &acpu;
void do_poll_C(M6502* cpu, int c) {
...
}

app/src/main/jni/main.c

JNIEXPORT jint JNICALL Java_com_littlefluffytoys_beebdroid_Beebdroid_bbcRun(JNIEnv * env, jobject  obj)
{
// Position independent code, hopefully!
the_cpu->c_fns = &fns; // +40
exec6502(the_cpu);
return the_cpu->pc_trigger_hit;
}

app/src/main/jni/Android.mk

# ... stuff ...
ifeq ($(TARGET_ARCH),arm)
LOCAL_CFLAGS +=  -march=armv6t2 -O9 -D_ARM_ -fPIC
LOCAL_SRC_FILES := 6502asm_arm.S
endif
ifeq ($(TARGET_ARCH),x86)
LOCAL_CFLAGS += -m32 -fPIC
LOCAL_SRC_FILES := 6502asm_x86.S
endif
# ... stuff ...

app/src/main/jni/Application.mk

# The ARMv7 is significanly faster due to the use of the hardware FPU
APP_ABI := armeabi-v7a x86
APP_PLATFORM := android-16
ifneq ($(APP_OPTIM),debug)
APP_CFLAGS += -O3 -fPIC
endif
APP_CFLAGS += -fPIC
LOCAL_SRC_FILES += 
6502.c 
main.c 
and_more_files.c

我记得在某个地方看到,我需要将LEA重写为下一条指令的CALL,从堆栈中弹出返回值,然后将CALL指令的地址与目标内存偏移量(也在文本段中)之间的差值相加:因为只需要在文本段内计算偏移量,所以绕过了链接器。(我只有一个LEA要重写:访问汇编代码中偏移量的跳转表——其他两个将变成一个输入参数和该参数指向的块中的指针,就像我为ARM所做的那样。)

链接器没有处理CALL到C函数,这让我更加困惑,因为ARM的加载程序很乐意重新定位BL指令。我已经在C编译标志中有了-fPIC。添加.global do_poll_C对该函数和其他函数没有区别。ARM对于外部C函数不需要.global声明。

我知道我可以传入一个用C函数指针初始化的块——我开始为ARM库这样做,但后来发现加载程序没有必要这样做。(我想我甚至可以向汇编语言表fns_asm添加一个C指针,因为C可能会找到汇编exec6502符号。)

我需要一个x86的函数指针块吗?或者有什么神奇的咒语可以让加载程序处理我的x86 CALL指令,就像BL在ARM中"只工作"一样?

谢谢你的帮助。

就像我怀疑的那样,您正在跨源文件(从asm到C)执行call操作,但不跨越共享对象边界,因此不需要运行时重新定位。使用__attribute__ ((visibility ("hidden"))),以便链接器可以在静态链接时解析call do_poll_C,而不让它在动态链接时参与符号插入。

或者更简单地说,使用-fvisibility=hidden -fvisibility-inlines-hidden更改默认值,只导出使用属性导出的少数符号。https://gcc.gnu.org/wiki/Visibility

我假设这在Android和GNU/Linux上是一样的如果不是这样的话,这个答案大部分都是无用的!


IDK为什么ARM会有所不同;也许Android/x86根本不愿意进行运行时重新定位。

在GNU/Linux(而不是Android)下,动态链接将在运行时修复64位绝对地址(或者我认为在32位模式下,32位绝对地址或相对地址)。

在64位模式下,call rel32无法达到任意64位目标,因此链接器不允许您对需要运行时重新定位的符号执行此操作。但这并不适用于你;您(出于某种原因)正在使用-m32制作过时的32位代码。


lea ebp,acpu // Causes PIC to fail与LEA无关,所有内容都与作为32位绝对地址的符号引用有关。

(此外,IDK为什么你会首先在那里使用LEA,因为你不能使用x86-64 RIP相对寻址;mov reg, imm32是将32位绝对地址放入寄存器的更有效方法:短1字节。)。

查看用于将地址放入寄存器的clang -m32 -fPIC代码:是的,它使用call/pop将EIP读取到寄存器中,并添加和偏移量以获得指向GOT的指针。然后,它可以处理与之相关的"私有"静态数据。检查编译器输出,或谷歌,以获得正确的语法,如lea ebp, [ebx + acpu@GOTOFF]或其他什么,给定ebx作为GOT指针。

GOT和GOTOFF之间的差异显示了AT&T语法:leal .LC0@GOTOFF(%eax), %edx将标签.LC0的地址获取到寄存器中,给定EAX作为GOT的指针。它还包括gcc的代码生成,用于使EAX指向GOT。

如果你使用x86-64,你可以使用RIP相对LEA,这会容易得多;那么您就不需要为了编写PIC代码而跳过重重关卡。lea rbp, [RIP + acpu]引用相对于当前地址的符号。

最新更新