diff options
Diffstat (limited to 'rt')
| -rw-r--r-- | rt/+x86_64/gdt16.S | 27 | ||||
| -rw-r--r-- | rt/+x86_64/gdt32.S | 20 | ||||
| -rw-r--r-- | rt/+x86_64/gdt64.S | 19 | ||||
| -rw-r--r-- | rt/+x86_64/halt.s | 5 | ||||
| -rw-r--r-- | rt/+x86_64/realcall.S | 161 | ||||
| -rw-r--r-- | rt/+x86_64/stage0.s | 92 | ||||
| -rw-r--r-- | rt/+x86_64/stage0_a20.S | 146 | ||||
| -rw-r--r-- | rt/+x86_64/stage0_drive.S | 118 | ||||
| -rw-r--r-- | rt/+x86_64/stage1.S | 191 | ||||
| -rw-r--r-- | rt/abort.ha | 3 | ||||
| -rw-r--r-- | rt/halt.ha | 1 | ||||
| -rw-r--r-- | rt/hare.sc | 74 | ||||
| -rw-r--r-- | rt/memset.ha | 6 | ||||
| -rw-r--r-- | rt/start.ha | 24 | ||||
| -rw-r--r-- | rt/test.ha | 1 |
15 files changed, 888 insertions, 0 deletions
diff --git a/rt/+x86_64/gdt16.S b/rt/+x86_64/gdt16.S new file mode 100644 index 0000000..6b74819 --- /dev/null +++ b/rt/+x86_64/gdt16.S @@ -0,0 +1,27 @@ +.globl gdt16 +gdt16: + .quad 0 +.globl gdt16_code +gdt16_code: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10011010 + .byte 0b10001111 + .byte 0x00 +.globl gdt16_data +gdt16_data: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10010010 + .byte 0b10001111 + .byte 0x00 +.globl gdtr16 +gdtr16: + .short gdtr16 - gdt16 - 1 + .int gdt16 + + + + diff --git a/rt/+x86_64/gdt32.S b/rt/+x86_64/gdt32.S new file mode 100644 index 0000000..21ab796 --- /dev/null +++ b/rt/+x86_64/gdt32.S @@ -0,0 +1,20 @@ +gdt32: + .quad 0 +gdt32_code: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10011010 + .byte 0b11001111 + .byte 0x00 +gdt32_data: + .short 0xFFFF + .short 0x0000 + .byte 0x00 + .byte 0b10010010 + .byte 0b11001111 + .byte 0x00 +.globl gdtr32 +gdtr32: + .short gdtr32 - gdt32 - 1 + .int gdt32 diff --git a/rt/+x86_64/gdt64.S b/rt/+x86_64/gdt64.S new file mode 100644 index 0000000..c6166bf --- /dev/null +++ b/rt/+x86_64/gdt64.S @@ -0,0 +1,19 @@ +gdt64: + .quad 0 +gdt64_code: + .short 0xFFFF + .short 0x0000 + .byte 0x0 + .byte 0b10011010 + .byte 0b10101111 + .byte 0x0 +gdt64_data: + .short 0xFFFF + .short 0x0000 + .byte 0x0 + .byte 0b10010010 + .byte 0b11001111 + .byte 0x0 +gdtr64: + .short gdtr64 - gdt64 - 1 + .int gdt64 diff --git a/rt/+x86_64/halt.s b/rt/+x86_64/halt.s new file mode 100644 index 0000000..72404b3 --- /dev/null +++ b/rt/+x86_64/halt.s @@ -0,0 +1,5 @@ +.globl rt.halt +rt.halt: + cli + hlt + jmp rt.halt diff --git a/rt/+x86_64/realcall.S b/rt/+x86_64/realcall.S new file mode 100644 index 0000000..46745f8 --- /dev/null +++ b/rt/+x86_64/realcall.S @@ -0,0 +1,161 @@ +.code64 + +.globl real.regs +real.regs: +reax: + .int 0x0 +rebx: + .int 0x0 +recx: + .int 0x0 +redx: + .int 0x0 +redi: + .int 0x0 +resi: + .int 0x0 + + +# :real_call +# This function is intended to be called from long mode +# and calls another function in real mode. Note that precautions have +# to be made. Recall that real mode can only access below 0xFFFFF. +# So the target function, and objects of arguments passed to it cannot +# be located above that. A reasonable scheme is to put the result +# in a buffer and then, once back in long mode, copy its content to higher +# above. +# The procedure to go from Long Mode back to Real Mode is explained in much details +# in the AMD64 Programmer's Manual Vol. 2, in particular, the figure 1-6. in section 1.3. +.globl real.call +real.call: + # :Push all the segments registers + push %rbx + push %r12 + push %r13 + push %r14 + push %r15 + push %di + + cli + + + lgdt gdtr32 + pushq $(gdt32_code - gdt32) + pushq $real_call_to_pmode_down + retfq +.code32 +real_call_to_pmode_down: + # :Here, we are in compatibility mode (32 bits) + + # :Disable paging + mov %cr0, %eax + and $~(1 << 31), %eax + mov %eax, %cr0 + + # :Disabling long mode in msr + mov $0xc0000080, %ecx + rdmsr + and $~(1 << 8), %eax + wrmsr + + # :Here we are in true protected mode (32 bits) + # Let's continue our descent to real mode + # by switching our gdt to 16 bits + lgdt gdtr16 + ljmp $(gdt16_code - gdt16), $real_call_to_16bits_pmode_down +.code16 +real_call_to_16bits_pmode_down: + + # :Disable protected mode + mov %cr0, %eax + and $~1, %eax + mov %eax, %cr0 + + ljmp $0, $real_call_to_16bits_rmode_down +real_call_to_16bits_rmode_down: + mov $0, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %gs + mov %ax, %fs + + # :real mode, yay + + lidt bios_idtr + + sti + + mov rebx, %ebx + mov recx, %ecx + mov redx, %edx + mov redi, %edi + mov resi, %esi + + pop %ax + call *%ax + + mov %eax, reax + mov %ebx, rebx + mov %ecx, recx + mov %edx, redx + mov %edi, redi + mov %esi, resi + + cli + + # :Restore protected mode + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + + lgdt gdtr32 + ljmp $(gdt32_code - gdt32), $real_call_to_pmode_up +.code32 +real_call_to_pmode_up: + # :Restore PAE (probably unneeded) + mov %cr4, %eax + or $(1 << 5), %eax + mov %eax, %cr4 + + # :Restore long mode + mov $0xc0000080, %ecx + rdmsr + or $(1 << 8), %eax + wrmsr + + # :Restore paging + mov %cr0, %eax + or $1 << 31, %eax + mov %eax, %cr0 + + lgdt gdtr64 + ljmp $(gdt64_code - gdt64), $real_call_to_longmode_up +.code64 +real_call_to_longmode_up: + mov $(gdt64_data - gdt64), %ax + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + mov %ax, %es + mov %ax, %ds + +real_call_end: + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbx + ret + +.globl testt +testt: + mov $1, %al + mov $0x43, %bh + mov $0x6, %ah + mov $0, %ch + mov $0, %cl + mov $5, %dh + mov $5, %dl + int $0x10 + ret diff --git a/rt/+x86_64/stage0.s b/rt/+x86_64/stage0.s new file mode 100644 index 0000000..de0b0e4 --- /dev/null +++ b/rt/+x86_64/stage0.s @@ -0,0 +1,92 @@ +.org 0x7c00 +.code16 +.section boot.stage0, "aw" + +.globl _stage0 +_stage0: + # :When we enter this procedure, we are + # in 16-bit real mode. Our CPU essentially + # thinks it is a 8086 and we have to work our + # way through enabling Long Mode, from which + # we will jump to the Hare code + + # :Clear segment registers + # this is important if memory accesses are done later before proper segmentation + # as these registers can have arbitrary values + xor %ax, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + xchg %bx, %bx + + sidt bios_idtr + + mov $stack_top, %sp + + # :Initialize the drive + # handling routines + call drive_init + + # :Enable the A20 line + call a20_enable_bios + call a20_check + cmp $1, %ax + je a20_ok + + call a20_enable_kb + call a20_check + cmp $1, %ax + je a20_ok + + call a20_enable_fast_gate + call a20_check + cmp $1, %ax + je a20_ok +a20_fail: + + # :We can't do anything, hang + # XXX error message? + hlt +a20_ok: + + # :Now that the drive is initialized and A20 set up, we shall load + # the rest :) +load_stage1: + # :Start to read at the second sector + mov $0x1, %di + # :Place the result just after us + mov $boot_stage1_start, %esi +load_stage1_loop: + # :When stage1 has finished loaded, finish looping + cmp $boot_end, %esi + jge load_stage1_loop_end + + call drive_read_lba + + inc %di + add $0x200, %esi + jmp load_stage1_loop +load_stage1_loop_end: + # :We will need the last sector for later, for when + # we want to load the stage3 + # :XXX can probably work this out reliably by division + mov %di, previous_sector + + # :Jump to the newly loaded code + jmp _stage1_real + +.include "rt/+x86_64/stage0_drive.S" +.include "rt/+x86_64/stage0_a20.S" + +previous_sector: + .short 0 +bios_idtr: + .quad 0 + +.org 510 +.word 0xaa55 + + +.include "rt/+x86_64/stage1.S" diff --git a/rt/+x86_64/stage0_a20.S b/rt/+x86_64/stage0_a20.S new file mode 100644 index 0000000..048670e --- /dev/null +++ b/rt/+x86_64/stage0_a20.S @@ -0,0 +1,146 @@ +# :This file contains functions pertaining to enabling +# the A20 line. Segmentation allows to access up to +# 20 bits of address space in 16 bit real mode, however +# when the segment and the address specified in a memory +# access is set to something that would theoritically +# point to something above 20bits (such as 0xFFFF:0x0010) +# the CPU would wrap over to the beginning of the memory +# (in the example, 0x0000:0x0000) +# Since some programmers relied on this behavior, when Intel +# added the possibility of addressing more than 20 bits, it +# had to keep that sepecific line disabled by default to +# remain backwards compatible. + +# :a20_enable_bios +# Tries to enable the A20 line with BIOS functions +a20_enable_bios: + mov $0x2401, %ax + int $0x15 + ret + +# :a20_enable_kb +# Tries to enable the A20 line using the keyboard controller +# (the physical line is routed and managed by the keyboard +# controller for some reason (ask Intel)) +a20_enable_kb: + cli + + # :Disable the PS/2 port + call a20_wait_ready + mov $0xad, %al + out %al, $0x64 + + # :Tell keybord controller to send output port byte + call a20_wait_ready + mov $0xd0, %al + out %al, $0x64 + + # :Get the output port byte + call a20_wait_avail + in $0x60, %al + push %eax + + # :Tell keyboard controller we are going to send new port byte + call a20_wait_ready + mov $0xd1, %al + out %al, $0x64 + + # :Send new port byte to keyboard controller + call a20_wait_ready + pop %eax + # :Enable A20 line bit in port byte + or $2, %al + out %al, $0x60 + + # :Reenable the PS/2 port + call a20_wait_ready + mov $0xae, %al + out %al, $0x64 + + call a20_wait_ready + + sti + ret + +# :a20_wait_ready +# Lock until keyboard controller input buffer is ready +a20_wait_ready: + in $0x64, %al + # :Bit 1 indicates whether input buffer is full + test $2, %al + jnz a20_wait_ready + ret + +# :a20_wait_avail +# Lock until keyboard controller output buffer has data pending +a20_wait_avail: + in $0x64, %al + test $1, %al + jz a20_wait_avail + ret + +# :a20_enable_fast_gate +# Tries to enable the A20 gate using the Fast Gate method +a20_enable_fast_gate: + in $0x92, %al + or $2, %al + out %al, $0x92 + ret + +# :a20_check +# Check if the A20 line is enabled +# returns %ax := status (1 OK, 0 disabled) +a20_check: + pushf + push %ds + push %es + push %di + push %si + + cli + + # :Put first segment in %es + xor %ax, %ax + mov %ax, %es + + + # :Put last segment in %ds + not %ax + mov %ax, %ds + + # :Select addresses + mov $0x0500, %di + mov $0x0510, %si + + # :Save previous bytes at %es:%di and %ds:%si + mov %es:(%di), %al + push %ax + + mov %ds:(%si), %al + push %ax + + # :Set different bytes at %es:%di and %ds:%si + # and check if they are the same + movw $0x00, %es:(%di) + movw $0x01, %ds:(%si) + + cmpw $0x01, %es:(%di) + + # :Restore previous bytes at %es:%di and %ds:%si + pop %ax + mov %al, %ds:(%si) + + pop %ax + mov %al, %es:(%di) + + # :Note: the following conditional jump relates to the cmp above! + mov $0, %ax + je a20_check_end + mov $1, %ax +a20_check_end: + pop %si + pop %di + pop %es + pop %ds + popf + ret diff --git a/rt/+x86_64/stage0_drive.S b/rt/+x86_64/stage0_drive.S new file mode 100644 index 0000000..1b6a801 --- /dev/null +++ b/rt/+x86_64/stage0_drive.S @@ -0,0 +1,118 @@ +# :Boot drive number +drive_no: + .byte 0 + +# :Boot drive sectors per track +drive_spt: + .byte 0 + +# :Boot drive number of heads +drive_heads: + .byte 0 + +# :These two values are of importance here +# to calculate the CHS triplets of the sectors we +# want to call from the LBA. +# There are 2 main ways to address a sector: +# (1) CHS (cylinder, head, sector) +# (2) LBA ("sector id") +# Since LBA is more intuitive, we would want to use it, +# however, it is not garenteed that the BIOS has functions to +# manipulate drives in terms of LBA, so we are going to convert LBA to CHS +# using those numbers + +# :drive_init +# Perform the initialization of the values above +drive_init: + # :Save the drive number + # the bios puts it in %dl + mov %dl, drive_no + + # :Perform the BIOS disk info call + mov $0x8, %ah + mov drive_no, %dl + int $0x13 + + # :Get the number of heads + # The BIOS functions gives us the index + # of the last entry (not the amount) (hence the + # incrementation) + inc %dh + mov %dh, drive_heads + + # :Get the sector per track count + and $0x3f, %cl + mov %cl, drive_spt + + ret + +# :drive_read_lba +# Reads the drive at an LBA address and writes it at %esi +# in %esi := the destination address +# out %di := the LBA +.globl drive_read_lba +drive_read_lba: + push %di + push %esi + + call drive_addr_to_arg + # The conversion formula is pretty simple + # and is described in great details at + # https://wiki.osdev.org/Disk_access_using_the_BIOS_(INT_13h)#CHS + + # However the idea is that sectors are layed out on several tracks + # + # and these tracks are layed out + + # :Calculate sector: LBA % sector per track + 1 (because 1-indexed) + mov %di, %ax + mov drive_spt, %cl + div %cl + + # :Perform the +1 + mov %ah, %cl + inc %cl # Sector + + # :Perfom (LBA / sector per track) + xor %ah, %ah + mov drive_heads, %dl + div %dl + + # :Store modulo in %dh, quotient in %ch + mov %ah, %dh # Head + mov %al, %ch # Cylinder + + # :The CHS is complete, now perform the BIOS call + mov $0x2, %ah + mov $1, %al + mov drive_no, %dl + int $0x13 + + pop %esi + pop %di + ret + +# :drive_addr_to_arg +# Since, nowadays, we can access 32-bit registers in real mode +# we use them instead of the more traditional %es:%bx for +# practical purposes. +# This function converts %esi into %es:%bx +# in %esi := address +# out %es:%bx := segmented address +drive_addr_to_arg: + push %esi + + # :Put %esi in %dx:%ax to perform division + mov %si, %ax + shr $16, %esi + mov %si, %dx + + # :Divide %dx:%ax by 16 + mov $16, %si + div %si + + mov %ax, %es + mov %dx, %bx + + pop %esi + ret diff --git a/rt/+x86_64/stage1.S b/rt/+x86_64/stage1.S new file mode 100644 index 0000000..4f99d69 --- /dev/null +++ b/rt/+x86_64/stage1.S @@ -0,0 +1,191 @@ +.code16 +.section boot.stage1, "aw" + +.include "rt/+x86_64/gdt16.S" +.include "rt/+x86_64/gdt32.S" +.include "rt/+x86_64/gdt64.S" +.include "rt/+x86_64/realcall.S" + +.code16 +_stage1_real: + xchg %bx, %bx + # :At this point, we are in the newly loaded code, but still in 16 bits Real Mode, + # we will want to switch to 32 bits protected mode + # This is done by + # (1) Loading the 32 bits GDT + # (2) Enabling protected mode bit + # (3) Loading segment registers with the data and code segment offsets + # in the GDT + + # Load the 32 bits GDT by using lgdt on the gdtr32 structure + lgdt gdtr32 + + # :Enable the protected mode bit is bit 1 in %cr0 + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + + # :Longjumping to set the %cs segment register + # (the rest will be set there) + ljmp $gdt32_code - gdt32, $_stage1 + + +# :Tell the assembler to emit 32 bits code from now on in the file +.code32 + +_stage1: + # :We are now in 32 bits protected mode! + # Our goal now is to load the stage 3 and to set up the long mode + # in order to get to our sweet 64 bits + xchg %bx, %bx + + # :Load the rest of the 32 bits data segments (%cs was set by the longjump) + mov $gdt32_data - gdt32, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + +pages_clear: + # :We know that the pages are located in free memory (in the 0x500 -> 0x7BFF zone) + # However, they might contain garbage so we need to free it + + xchg %bx, %bx + mov $_p4, %edi + call pages_clear_page + + mov $_p3, %edi + call pages_clear_page + + mov $_p2, %edi + call pages_clear_page + + mov $_p1, %edi + call pages_clear_page + +pages_fill: + # :Set each page level's first entry to point to the next level + # in order to identity map the first 2 megabytes of memory + + # :Here, we or with 3 in order to enable the + # 'present' bit and the 'rw' bit on the page entry + mov $_p3, %edi + or $3, %edi + mov %edi, (_p4) + + mov $_p2, %edi + or $3, %edi + mov %edi, (_p3) + + mov $_p1, %edi + or $3, %edi + mov %edi, (_p2) + + + # :Now we are going to fill the first page + mov $0x3, %ebx + mov $512, %ecx + mov $_p1, %edi +pages_fill_p1_loop: + mov %ebx, (%edi) + add $0x1000, %ebx + add $8, %edi + loop pages_fill_p1_loop + + # :Now that the pages are prepared, we must set the %cr3 to point to the level 4 + # page table. This register contains the address of the level 4 page + mov $_p4, %edi + mov %edi, %cr3 + +pages_enable_pae: + # :Enable the physical address extension, extends the virtually addressable + # space above 4GB. Also, this is required for long mode + # The PAE bit is the 5th bit of the %cr4 register + mov %cr4, %eax + or $1 << 5, %eax + mov %eax, %cr4 + +pages_enable_longmode_bit: + # :Enable the longmode bit. It is the 8th bit of the model specific register + # identified with 0xC00000080 + mov $0xC0000080, %ecx + rdmsr + or $1 << 8, %eax + wrmsr + + # :We are now in Compatibility mode. This is a transitational mode between 32 bits protected + # and long mode. We still have 2 things to do before entering long mode: + # (1) Enabling paging + # (2) Loading a 64 bits GDT + +pages_enable_paging: + # :Enable the paging and also make sure that the protected mode is enabled + # Everything is in %cr0, paging is bit 31st and protected is bit 0th + mov %cr0, %eax + or $1 << 31 | 1 << 0, %eax + mov %eax, %cr0 + +reload_segments: + # :Last thing to do, reloading the segments + xchg %bx, %bx + + lgdt gdtr64 + + ljmp $gdt64_code - gdt64, $stage1_long + + +# :pages_clear_page: +# Clears the page located at address +# pointed by %edi +pages_clear_page: + push %ecx + push %edi + + mov $0x1000, %ecx +pages_clear_page_loop: + movb $0x0, (%edi) + inc %edi + loop pages_clear_page_loop + + pop %edi + pop %ecx + ret + +# :Tell the assembler to emit 64 bits code +.code64 + +stage1_long: + # :Finally! We are in long mode. We shall now setup the rest of the segments + # registers, as well as the stack, then jump to Hare + + xchg %bx, %bx + + mov $gdt64_data - gdt64, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %ss + mov %ax, %fs + mov %ax, %gs + + # :Clear all the registers + # (QBE makes this assumption?) + xor %rax, %rax + xor %rbx, %rbx + xor %rcx, %rcx + xor %rdx, %rdx + xor %rdi, %rdi + xor %rsi, %rsi + xor %r8, %r8 + xor %r9, %r9 + xor %r10, %r10 + xor %r11, %r11 + xor %r12, %r12 + xor %r13, %r13 + xor %r14, %r14 + xor %r15, %r15 + xor %rbp, %rbp + + cli + + jmp _hare diff --git a/rt/abort.ha b/rt/abort.ha new file mode 100644 index 0000000..dbe5d76 --- /dev/null +++ b/rt/abort.ha @@ -0,0 +1,3 @@ +export @noreturn fn abort_fixed() void = { + halt(); +}; diff --git a/rt/halt.ha b/rt/halt.ha new file mode 100644 index 0000000..228beb8 --- /dev/null +++ b/rt/halt.ha @@ -0,0 +1 @@ +export @noreturn fn halt() void;
\ No newline at end of file diff --git a/rt/hare.sc b/rt/hare.sc new file mode 100644 index 0000000..0cb9c02 --- /dev/null +++ b/rt/hare.sc @@ -0,0 +1,74 @@ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(_stage0) + +PHDRS { + headers PT_PHDR PHDRS; + text PT_LOAD FILEHDR PHDRS; + data PT_LOAD; +} + +SECTIONS { + . = 0x0; + + . = 0x500; + . = ALIGN(4096); + _p4 = .; + . += 4096; + _p3 = .; + . += 4096; + _p2 = .; + . += 4096; + _p1 = .; + . += 4096; + + . = 0x7c00; + stack_top = .; + + boot_start = .; + + boot : { + boot_stage0_start = .; + *(boot.stage0) + boot_stage0_end = .; + + boot_stage1_start = .; + *(boot.stage1) + boot_stage1_end = .; + + . = ALIGN(512); + } + + .text : { + KEEP (*(.text)) + *(.text.*) + } :text + .data : { + KEEP (*(.data)) + *(.data.*) + . = ALIGN(16); + KEEP (*(.exception_array)) + } :data + .init_array : { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } :data + .fini_array : { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } :data + .test_array : { + PROVIDE_HIDDEN (__test_array_start = .); + KEEP (*(.test_array)) + PROVIDE_HIDDEN (__test_array_end = .); + } :data + + .bss : { + KEEP (*(.bss)) + *(.bss.*) + } :data + + + boot_end = .; +} diff --git a/rt/memset.ha b/rt/memset.ha new file mode 100644 index 0000000..01b9946 --- /dev/null +++ b/rt/memset.ha @@ -0,0 +1,6 @@ +export fn memset(dest: *void, val: u8, amt: size) void = { + let a = dest: *[*]u8; + for (let i = 0z; i < amt; i += 1) { + a[i] = val; + }; +}; diff --git a/rt/start.ha b/rt/start.ha new file mode 100644 index 0000000..edbc5a5 --- /dev/null +++ b/rt/start.ha @@ -0,0 +1,24 @@ +@symbol("main") fn main() void; + +const @symbol("__init_array_start") init_start: [*]*fn() void; +const @symbol("__init_array_end") init_end: [*]*fn() void; +const @symbol("__fini_array_start") fini_start: [*]*fn() void; +const @symbol("__fini_array_end") fini_end: [*]*fn() void; + +export @noreturn @symbol("_hare") fn _hare() void = { + const ninit = (&init_end: uintptr - &init_start: uintptr): size + / size(*fn() void); + for (let i = 0z; i < ninit; i += 1) { + init_start[i](); + }; + + main(); + + const nfini = (&fini_end: uintptr - &fini_start: uintptr): size + / size(*fn() void); + for (let i = 0z; i < nfini; i += 1) { + fini_start[i](); + }; + + halt(); +}; diff --git a/rt/test.ha b/rt/test.ha new file mode 100644 index 0000000..a9a03a3 --- /dev/null +++ b/rt/test.ha @@ -0,0 +1 @@ +export @symbol("real_call") fn real_call() void; |
