Can dmd compile with my own runtime library?

Frits van Bommel fvbommel at REMwOVExCAPSs.nl
Thu Aug 23 08:47:15 PDT 2007


Huang F Guan wrote:
> Hi, I solved one program now, you are right. My gdc source code is 2007-8-22 and my gdc executable files are a little old.
> 
> After I updated my gdc, I still have many troubles. This time I couldn't link it. There are many undefined refereces. I don't know what the real work of these functions do. I don't know whether is proper to write some dummy functions for them.
> 
[snip errors]
> 
> Really, it's hard for me to read the phobos sources. 

The easiest way to find out what a particular runtime function does is 
really just to read the runtime sources provided with the compiler.
A tool like grep is really helpful here, especially since most runtime 
functions (the ones starting with '_d_' aren't mangled, and their name 
appears literally in the source.

By the way, The symbols looking like _D*TypeInfo* means you need some 
extra typeinfo classes. They're in phobos/std/typeinfo and 
tango/lib/compiler/{dmd,gdc}/typeinfo. You shouldn't need much more than 
memcmp and a string-comparison function to get them to compile and link.

Looking at the missing the _d_* symbols, it looks like you're using the 
synchronized keyword (on objects), which references the _d_monitor* 
symbols. You'll either need to implement those for your platform so they 
perform locking[1] or stop using synchronized.
_d_array{catn,append,appendc}T, _d_newarrayT, _d_delmemory may require 
attention because they deal with dynamically allocated memory.
_d_dynamic_cast and the routines it requires can probably be lifted 
straight from Phobos or Tango without any problems, they just read 
ClassInfo structures and perform some pointer manipulations.
_d_array_bounds is just a function that gets called when array indices 
are out of bounds. The standard implementation just throws an error but 
you can perform any other kind of error handling here (printing a 
message to screen and hanging can be used, for instance, as a debugging 
tool that preserves the stack so you can use a debugger connected to an 
emulator to find out where it happened)

[1]: IIRC they should be recursive, allowing the same thread to lock it 
multiple times as long as it unlocks it as many times afterwards.

> From what you said, I think you might have done a simple runtime library. If so, would you send me one? 

Like I said, I switched to just using Tango. It's quite easy to port. In 
fact, looking at my diff viewer, I mostly just copied the posix.mak 
files to MyOS.mak and added  -D__MyOS__ to CFLAGS and -version=MyOS to 
the other *FLAGS and then commented and versioned out some code that 
needed stuff I didn't support yet (locking) and/or didn't need anyway 
(floating point support).
In my kernel sources I added an empty "extern(C) void thread_init()" 
(with TODO comment), implemented malloc, calloc and free using my kernel 
allocation functions, and added this into a file called init.d:
---
import multiboot;
import console;
import mem.init;

extern (C) bool rt_init( void delegate( Exception ) dg = null );
extern (C) bool rt_term( void delegate( Exception ) dg = null );

int main(char[][] args);

extern(C) void init() {

     extern(C) void rtinitExceptionHandler(Exception e) {
         writefln("Uncaught exception in runtime: {}", e);
         throw new Exception("Exception during runtime initialization", e);
     }

     try {
         mem_init();

         rt_init(&rtinitExceptionHandler);

         /// Shouldn't return...
         main(multiboot.commandLine());

     } catch (Exception e) {
         writefln("Uncaught exception: {}", e);
     } catch (Object o) {
         writefln("Uncaught non-Exception exception: {}", o);
     }
     writeln("main() returned, halting processor.");
     for(;;) asm { cli; hlt; }
}
---
Notes:
* the imports at the top are some of my kernel modules.
* mem_init also initializes my multiboot module which it uses to get the 
memory address ranges that are free (the elf header is also parsed to 
find where kernel code and static data is located, and multiboot modules 
should also be taken into account if you support them)
* 'init' is called by my asm code after it calls all the pointers in the 
.ctors section.
* 'rt_init' is from Tango and calls the static constructors of all 
modules that have one.
* IIRC exception handling "just worked"[1], but make sure you catch 
exceptions in your interrupt handlers and the top-level function in 
every thread.
* the write* functions are *really* easy to implement with the help of 
tango.text.convert.Layout and a way to print characters to the screen :).


[1]: I already knew to not throw away the .deh* sections of my 
executable in my linker script. Speaking of which, here it is:
---
OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
phys = 0x00100000;
virt = 0xC0000000;

SECTIONS
{
     . = phys + virt;

     /* Multiboot Header */
     mb-header : AT(ADDR(mb-header) - virt)
     {
         /* Multiboot header, must be 4-byte aligned and in first 8k of 
file. */
         . = ALIGN(4);
         KEEP(*(mb-header))    /* not referenced by code, just by 
bootloader (i.e. GRUB) */
     }

     .text : AT(ADDR(.text) - virt)
     {
         code_begin = .;

         *(.text* .gnu.linkonce.t*)
     }

     . = ALIGN(4096);

     .data : AT(ADDR(.data) - virt)
     {
         *(.data* .gnu.linkonce.d*)
     }

     /* For some reason .ctors isn't marked read-only, so group with 
.data */
     .ctors : AT(ADDR(.ctors) - virt)
     {
         start_ctors = .;
         KEEP(*(.ctor*))     /* not referenced except through 
surrounding labels */
         end_ctors = .;
     }

     . = ALIGN(4096);

     /* Read-only data right before uninitialized data, as that contains the
      * (down-growing) stack. This way, we can put the read-only data on a
      * non-writable page and catch stack overflows
      */
     .rodata : AT(ADDR(.rodata) - virt)
     {
         *(.rodata*)
	
     }

     /* Exception handling data */
     .deh_eh : AT(ADDR(.deh_eh) - virt)
     {
         _deh_beg = .;
         KEEP(*(.deh_beg))
         KEEP(*(.deh_eh))
         _deh_end = .;
         KEEP(*(.deh_end))
     }

     . = ALIGN(4096);

     .bss : AT(ADDR(.bss) - virt)
     {
         *(.bss*)
     }

     /DISCARD/ :
     {
         *(.comment)
     }

     kernel_end = .;
}
---
Also note that my initial stack is in .bss, at the start, and that right 
before .bss is read-only data. One of my modules sets the pages 
containing the latter to read-only so stack overflows are detected. This 
is optional and putting all read-only stuff together may keep the kernel 
size down a bit.
I also put the multiboot header in its own section to make sure it's put 
as far to the front of the executable as possible.
My startup asm (that declares the 'start' entry symbol) performs some 
initialization including stack, GDT (+ segment registers) and (simple, 1 
4MB-page mapped at both 0 and 0xC000_0000) paging (and assigning the 
multiboot signature and data address to variables defined in my 
'multiboot' module), calls every function pointer from start_ctors to 
end_ctors (as defined in the linker script), then calls my 'init' 
function posted above.



More information about the Digitalmars-d mailing list