Оглавление
§ GCC
Стандартный шаблон для компилятора gcc:
RC=riscv64-unknown-elf
LB=-nostartfiles -nostdlib -nodefaultlibs
all: comp dump
comp:
$(RC)-gcc -march=rv32i_zicsr -mabi=ilp32 $(LB) -T linker.ld -Os crt.s main.c -o main.elf -lgcc
$(RC)-objcopy -O binary main.elf main.bin
dump:
$(RC)-objdump -S main.elf > dump.lst
Пояснения к компиляции:
-march=rv32i_zicsr Включение набора команд процессора RV32I, базовый набор даже без умножения
-mabi=ilp32 Принудительное включение 32 битных вычислений, основанное только на integer (i), в том числи длинные целые long тоже 32 бит (l), и указатели (p32), без поддержки FPU, которое эмулируется программно
-nostartfiles -nostdlib -nodefaultlibs Не подключать библиотеку stdlib и прочие
-T linker.ld Использовать конфигурации линковщика из файла linked.ld (можно задать самому)
-Os Оптимизация по размеру кода, уменьшить код до предела
crt.s Стартовый код, расширение обязательно должно начинаться с маленькой буквы .s для прямого встраивания в ассемблер
main.c и -o main.elf Входящий и исходящий готовый файл
-lgcc Добавляется либа для поддержки эмуляции FPU
Опционально:
objcopy Извлечение бинарного кода из main.elf в файл main.bin для запуска на эмуляторах
objdump Для того чтобы посмотреть ассемблерный листинг
§ crt.s
Код запуска, инициализации.
.section .text.init # Название данной секции
.global _start # Метка _start будет видна везде
.align 4 # Указать что выровнено по 4 байта
_start: la sp, _stack_top
call main
loop: j loop
§ main.c
Ниже представлен код, который выведет "Hello World" по адресу IBM VGA Textmode 0xB8000:
void main()
{
heaph(vm, 0xB8000);
char *str = "Hello World!";
while (*str) {
*vm = (*str | 0x1700);
str++;
vm++;
}
}
Могут потребоваться макросы:
#define heap(A,B,C) volatile unsigned A* B = (unsigned A*) C
#define heapb(A,B) heap(char,A,B)
#define heaph(A,B) heap(short,A,B)
#define heapw(A,B) heap(int,A,B)
#define brk asm("ebreak")
#define csr_read(reg) ({ unsigned int __v; asm volatile ("csrr %0, " #reg : "=r"(__v)); __v; })
#define csr_swap(reg,val) ({ unsigned int __v; asm volatile ("csrrw %0, " #reg ", %1" : "=r"(__v) : "rK" (val)); __v; })
#define csr_write(reg, val) asm volatile ("csrw " #reg ", %0" : : "rK"(val))
#define csr_set(reg, mask) asm volatile ("csrs " #reg ", %0" : : "rK" (mask))
#define csr_clr(reg, mask) asm volatile ("csrrc " #reg ", %0" : : "rK" (mask))
§ Linker.ld
Общий шаблон линкера.
PHDRS
{
text_pt PT_LOAD FLAGS(5); /* 101 RX для кода */
data_pt PT_LOAD FLAGS(6); /* 110 RW для данных */
}
SECTIONS
{
. = 0x0; /* Стартовый адрес */
.text : { *(.text.init) *(.text) *(.text.*) } : text_pt
.rodata : { *(.rodata .rodata.*) } : text_pt
.data : { *(.data) } : data_pt
.bss : { *(.bss) } : data_pt
_stack_top = 0x10000;
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) }
}
Указываются важные точки:
- Назначение лейблов с разными правами доступа в памяти (text_pt, data_pt)
- Стартовый адрес кода начинается с
0x0
- Сначала идет метка
text.init (чтобы начиналась с 0x0 в crt.s), а потом уже весь остальной код
- Потом идут rodata (read only data – константы), сама data (данные) и bss (секция, которая должна быть заполнена нулями во что бы то ни стало)
- И начальная высота стека
0x10000
Для каждой секции (.text, .rodata, ...) дополнительно указываются права доступа – данные или код.
§ Пример ECALL
В GCC необходимо правильно пользоваться системными вызовами, чтобы избежать падения системы при компиляции. Регистры должны быть правильно распределены и записаны. Ниже пример, как пользоваться вызовом ECALL. Всего можно задать до 7 аргументов и в регистре a7 будет номер системного вызова.
int syscall(int fd, const void *buf, int count)
{
register int r_a0 __asm__("a0") = fd;
register int r_a1 __asm__("a1") = (int) buf;
register int r_a2 __asm__("a2") = count;
register int r_a7 __asm__("a7") = 64;
__asm__ volatile(
"ecall"
: "+r" (r_a0)
: "r" (r_a1), "r" (r_a2), "r" (r_a7)
: "memory"
);
return r_a0;
}
Вот только для самого обработчика исключений придется делать ассемблерный код, который будет:
- Сохранять в стек значения
a1..a7 (если надо)
- Проверять причину исключения, читая например
csrr a0, mcause
- Вызывать нужный обработчик через
call [c_function]
- Прибавить
mepc+4, восстановить значения из стека и вернуться в программу через mret