我成功地将Android项目的ARM库更改为PIC(位置无关代码),因为我想进行一些不相关的修复,而Android自Lollipop以来只支持PIC库。(我的叉子的最新来源是http://github.com/sleekweasel/Beebdroid)
现在,我也想在整理和编写pull请求之前让x86也工作起来(因为它在原始项目中),但我的x86比我的ARM知识要弱得多,安卓汇编文章似乎只处理ARM。
我遇到问题的两条指令是LEA
和CALL
:它们导致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]
引用相对于当前地址的符号。