Friday, January 26, 2024

[SOLVED] How to load a variable's address into a register using assembly? (arm64 linux kernel code)

Issue

Back to assembly code after a while..
I have put this code right after the linux kernel starts (linux-5.15.68).

.global mydebug2
....(skip)
SYM_CODE_START(primary_entry)
    mov x27, #0x3333
    ldr x28, mydebug2
    add x28, x28, #8
    str x27, [x28], #8
    ldr x26, =myptr
    str x28, [x26]
    b .

    bl  preserve_boot_args
    bl  init_kernel_el          // w0=cpu_boot_mode

The buffer "mydebug2" is defined in init/main.c like this.

#include <test/mwtest.h>
uint64_t mydebug2[MWBUF_LENGTH] = {0,};
uint32_t myidx = 0;
uint64_t mycnt = 0; // for left shift, 64bit

asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
{
    char *command_line;
    char *after_dashes;

When I compile it, I get this error at the end.

LD .tmp_vmlinux.kallsyms1 arch/arm64/kernel/head.o: in function primary_entry': /home/ckim/prj1/LinuxDevDrv/linux-5.15.68/arch/arm64/kernel/head.S:97:(.init.text+0x4): relocation truncated to fit: R_AARCH64_LD_PREL_LO19 against symbol mydebug2' defined in .bss section in init/main.o make: *** [Makefile:1214: vmlinux] Error 1

I guess mydebug2 is virtual address (like 0xffffffc008c4b028), and ldr x28, mydebug2 instruction cannot load that address into x28. How can I load the address to x28?

(BTW, I know in the current setting, how the physcial address is mapped into kernel virtual address. I see 0xffffffc008000000 corresponds to physcial memory 0x80200000).

ADD : as Nate Eldredge suggested I tried using the adrp and add pair which is defined in arch/arm64/include/asm/assembler.h as below.

/*
 * Pseudo-ops for PC-relative adr/ldr/str <reg>, <symbol> where
 * <symbol> is within the range +/- 4 GB of the PC.
 */
    /*
     * @dst: destination register (64 bit wide)
     * @sym: name of the symbol
     */
    .macro  adr_l, dst, sym
    adrp    \dst, \sym
    add \dst, \dst, :lo12:\sym
    .endm
    .... more (skip) ...

Solution

The most common way to generate an address in a register on ARM64 is pc-relative, using adrp. The ldr x28, =mydebug2 syntax, to load from the literal pool, is usually also an option, but in this case, it seems that the kernel's relocation fixups have not been done yet, so that won't work.

So following Understanding ARM relocation (example: str x0, [tmp, #:lo12:zbi_paddr]), you want to do

    mov x27, #0x3333
    adrp x28, mydebug2
    add x28, x28, #:lo12:mydebug2
    add x28, x28, #8
    str x27, [x28], #8

Since the complete relative address won't fit in one instruction, adrp gives you the 21 high bits, and #lo12:mydebug2 is the 12 low bits.

Actually, you can save one instruction by building the +8 into the address computation:

    mov x27, #0x3333
    adrp x28, mydebug2+8
    add x28, x28, #:lo12:mydebug2+8
    str x27, [x28], #8

(Note the +8 needs to be in both places to account for the possibility that the +8 causes a carry out of the low 12 bits, which is all that add can encode as an immediate.)

You'll need to do the same thing with myptr on the next line.



Answered By - Nate Eldredge
Answer Checked By - Pedro (WPSolving Volunteer)