[MLton] Re: MLton and shared libraries

Jens Axel Søgaard jensaxel@soegaard.net
Fri, 22 Apr 2005 23:44:43 +0200


Stephen Weeks wrote:

>>Program received signal SIGSEGV, Segmentation fault.
>>Chunk0 () at test.1.c:5084
>>5084            S(Word32, 0) = 0;

> This looks like the first call to the GC in the program, which is to
> make space to allocate the globals (presumably there is a limit check
> that fails and jumps to L_1).  The "S(Word32, 0) = 0" stores the
> return address (0) on the stack.  The return address (0) is handled 
> by the "case 0:", which will jump to the right return code (L_0),
> although in this case, the program isn't multithreaded, so MLton knows
> to generate code to fall through to the L_0 block without ever
> checking the return address.
> 
> Here are some suggestions.
> 
>  * step through GC_init and make sure it's actually running and that
>    the stack is allocated.
>  * step through the MLton-generated C code starting as soon as Chunk0
>    is entered and verify that the stack looks good.

Since the main program is mzscheme, the GC_init init_function are loaded
while mzscheme is run. Setting breakpoints in something to be loaded is
at best tricky - maybe gdb can, but I couldn't figure out how.

In stead I decided to test init_function in a "normal" program. I
changed main() to call init_functions and commented the constructor
attribute out temporarily:

modify main() in c-main.h to call init_function,
temporarily disabling

int main (int argc, char **argv) {                                      \
         struct cont cont;                                               \
         fprintf(stderr, "MAIN1\n");                                     \
         fflush(stderr);                                                 \
         init_function(1);                                               \
         fprintf(stderr, "MAIN2\n");                                     \
         fflush(stderr);                                                 \
...

void                                                                    \
/* __attribute__((constructor))  */                                          \
init_function (int dummy)                                               \
{                                                                       \
         int argc=1;                                                     \
         void *argv[2];                                                  \
         struct cont cont;                                               \
         argv[0] = "bar";                                                \
         argv[1] = NULL;                                                 \
             errPrintFlush("init_function> START\n");         \
         Initialize (al, cs, mg, mfs, mmc, pk, ps);                      \
             errPrintFlush("init_function> before real_Init\n");         \
         real_Init();                                                    \
             errPrintFlush("init_function> after real_Init\n");          \
         PrepFarJump(mc, ml);                                            \
             errPrintFlush("init_function> before trampoline\n");        \
         /* Trampoline */                                                \
         while (1) {                                                     \
                     errPrintFlush("init_function> trampoline 1\n");     \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                     errPrintFlush("init_function> trampoline 2\n");     \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                     errPrintFlush("init_function> trampoline 3\n");     \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
                 cont=(*(struct cont(*)(void))cont.nextChunk)();         \
         }                                                               \
             errPrintFlush("init_function> After main\n");               \
}                                                                       \

I let the test program end in a print statement, in order to see whether
the program was run.

bash-2.05b# cat test.sml
val e = _export "foo": int -> int;
val _ = e (fn (i) => 42);
print "TESTING 1 2 3\n";

First I tried compiling it with normal options (i.e. no use of -shared-library):

bash-2.05b# ../build/bin/mlton -codegen c -cc-opt -g -keep g -verbose 1 -default-ann 'allowExport true' test.sml 


later I tried adding -shared-library true to the list of options.

Both times I got the same output from ./test:

bash-2.05b# ./test
MAIN1
init_function> START
init_function> before real_Init
init_function> after real_Init
init_function> before trampoline
init_function> trampoline 1
TESTING 1 2 3
bash-2.05b#

NOTE: We don't see MAIN2 here.


Both programs, when from within gdb, produces the same output, and gdb writes

     Program exited normally.

at the end.


What troubles me is that MAIN2 is not displayed. Since MzScheme plays tricks
with the C-stack it is important that foreign calls doesn't leave stuff
anything but the return value on top of the C stack.


-- 
Jens Axel Søgaard