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