How to loop over C-array using embed assembler (aarch64)?

88 Views Asked by At

I have the next awful code:

void print(const char* text) {
    unsigned long address = 0x9000000;
    unsigned long counter = 0;
    char ch = *text;
    while (ch != '\0') {
        address += counter;
        asm volatile(
            "MOV X10, %[address];"
            "MOV W9, %w[ch];"
            "STRB W9, [X10];"
            :[address]"+r"(address), [ch]"+r"(ch)
        );
        ++counter;
        ch = *(text + counter);
    }
}

void start(void) {
    print("Hello, World!");
}

Unfortunately, even this code doesn't work correctly and prints: Hel instead of Hello, World!

However, I would prefer to re-write all loop logic in the embed/inline assembler but I can't get how to read and iterate over C-array (text) in assembler. I've tried a lot of variants and all successfully failed.

This code was created for aarch64 (cortex-a72) and it's launched in Qemu by command:

qemu-system-aarch64 -M virt \
    -cpu cortex-a72 \
    -bios "/opt/homebrew/Cellar/qemu/$(QEMU_VERSION)/share/qemu/edk2-aarch64-code.fd" \
    -m 128M \
    -nographic \
    -device loader,file=$(BUILD_DIR)/kernel.elf \
    -device loader,addr=0x40100000,cpu-num=0

P.S. I use LLVM clang and aarch64-elf-binutils compilers and linker on macOS (M1 Pro) to build kernel.elf.

2

There are 2 best solutions below

5
Denis Steinman On

Oh, maybe someone is the same noobie like me and it will help him.

Firstly, the solution:

void print(const char* text) {
    unsigned long address = 0x9000000;
    asm volatile(
        "MOV X10, %[address];"
        "MOV X11, %[text];"
        :[address]"+r"(address), [text]"+r"(text)
    );
PRINT_STR:
    asm volatile(
        "LDR X9, [X11];"
        "STRB W9, [X10];"
        "ADD X11, X11, #0x1;"
    );
    asm goto ("CBNZ W9, %l0"::::PRINT_STR);
}

void start(void) {
    print("Hello, World!");
}

How does it work?

  1. Declare the address of video memory (fix me, plz, if I am wrong). In our case is 0x9000000.
  2. Load address and text variables into X10 and X11 registers (there is no strict rules which registers you will use). Note text is a pointer so in fact we loaded an address of our string into register, not a value.
  3. Declare a label to jump into it later.
  4. Load into X9 register the next string character that's located by an address in X11 register.
  5. Copy the loaded value to 0x9000000.
  6. Increment X11 register's value, in other words we just switch our pointer to the next character in the string.
  7. Finally, jump into PRINT_STR if the W9 register contains non-zero value (C-strings end with \0).

UPD: Second thoughts are best... I wanted to use assembler due to I thought C code will be ugly and still mixed with assembler. But I forgot it's mr. "C", pointers are the power!

void print(const char * text) {
    char * address = (char *)0x9000000;
    const char * ch = text;
    while (*ch != '\0') {
        *address = *ch;
        ++ch;
    }
}
0
chux - Reinstate Monica On

address is not incremented properly.

Let us view this with printing to stdout.

#include <stdio.h>

void my_print(const char* text) {
    unsigned long address = 0x9000000;
    unsigned long counter = 0;
    char ch = *text;
    while (ch != '\0') {
        address += counter;
#if 0
        asm volatile(
            "MOV X10, %[address];"
            "MOV W9, %w[ch];"
            "STRB W9, [X10];"
            :[address]"+r"(address), [ch]"+r"(ch)
        );
#endif
        printf("ch:%3d %c address:%lX counter:%lX\n", ch, ch, address, counter);
        ++counter;
        ch = *(text + counter);
    }
}

//void start(void) {
int main(void) {
  my_print("Hello, World!");
}

Output

ch: 72 H address:9000000 counter:0
ch:101 e address:9000001 counter:1
ch:108 l address:9000003 counter:2
ch:108 l address:9000006 counter:3
ch:111 o address:900000A counter:4
ch: 44 , address:900000F counter:5
ch: 32   address:9000015 counter:6
ch: 87 W address:900001C counter:7
ch:111 o address:9000024 counter:8
ch:114 r address:900002D counter:9
ch:108 l address:9000037 counter:A
ch:100 d address:9000042 counter:B
ch: 33 ! address:900004E counter:C

I suspect OP only wants address changed by 1 after printing.

// address += counter;
++address;  // Move to after "asm volatile" printing.
    while (ch != '\0') {
        asm volatile(
            "MOV X10, %[address];"
            "MOV W9, %w[ch];"
            "STRB W9, [X10];"
            :[address]"+r"(address), [ch]"+r"(ch)
        );
        ++address;
        ++counter;
        ch = *(text + counter);
    }