I successfully changed the ARM library of an Android project into PIC (position independent code) because I wanted to make some unrelated fixes and Android only supports PIC libraries since Lollipop. (My fork's latest source is http://github.com/sleekweasel/Beebdroid)
Now I want to get x86 working too before I tidy up and cook a pull request (because it's in the original project) but my x86 is rather weaker than my ARM knowledge, and Android assembly articles only seem to handle ARM.
The two instructions I'm having problems with are LEA
and CALL
: they cause ld
to emit
warning: shared library text segment is not shareable
If I comment them out then ./gradlew build
happily links, but obviously the code doesn't work quite as well.
Here are snippets from the project - maybe they'll be clearer than my English description. I think this is complete context, since it's only the instructions and their interaction with the linker that's at issue:
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
I recall seeing somewhere that I need to rewrite LEA
into a CALL
to the next instruction, pop the return value off the stack, and then add the difference between the address of the CALL
instruction and the target memory offset (also in the text segment): because only computed offsets within the text segment are needed, the linker is circumvented. (I only have one LEA
to rewrite: to access a jump table of offsets within the assembly code - the other two will turn into an input parameter and a pointer in a block pointed to by that parameter, as I've done for ARM.)
I'm more confused by CALL
to C functions not being handled by the linker, because ARM's loader is happy to relocate BL
instructions. I already have -fPIC
in the C compile flags. Adding .global do_poll_C
makes no difference for that function and presumably the others. ARM doesn't need .global
declarations for the external C functions.
I'm aware that I could pass in a block initialised with C function pointers - I started doing that for the ARM library but then found that the loader made that unnecessary. (I presume I could even add a C pointer to the assembly language table fns_asm
, since C presumably finds the assembly exec6502
symbol.)
Do I need a function pointer block for x86, or is there some magic incantation I can use to ask the loader to handle my x86 CALL instructions, the way BL 'just works' with ARM?
Thanks for any help.