HOWTO fix RVCT 3.0 armlink error: L6218E: Undefined symbol main (referred from kernel.o)
(and other ARM-RVCT/BREW topics)

Back to home page.

Original: 11 Feb 2010

For some reason I find myself back making a few BREW (4.01sp5) applictions. You'd think that with Android and iPhone (and the canceling of the BREW conferences in San Diego, and MP not on fire) the whole platform would be on the way out, but there are still parts of the world where BREW thrives, so here I am.

Once again, the application is ridiculously complicated consisting of multiple interacting applets and various custom extension modules. This time, however, I am using the ultra-modern RVCT 3.0 toolchain from ARM Ltd (and the full version too, not the BREW subset).

As with the GNU compilers, there are two kinds of MOD you can compile. Old school, using position independent code (ropi) that requires no relocation "fixing up" at run time (because everything is accessed as offsets from the PC), and elf2mod style that have startup code attached to fix the relocations to use the final run-time load address.

With ropi style, you can't use global variables, or static initializers, or, AFAIKT, C++ because the vtables don't work. So we wound up using elf2mod style for this project. (That tool seems to be improved since I wrote the main GNU pages on this site in 2006. The version I have is 163,840 bytes.)

With ropi, you use fromElf to whack the ELF header off the linker output file and combine all the sections in the ELF file into a simple binary image. With elf2mod you use (wait for it...) elf2mod to process the reloactions and and add startup code to the applet (it also strips the ELF header).

To do a ropi compile, I've been using:

CC=armcc -c --littleend --cpu ARMxxx --apcs /ropi/interwork/norwpi --split_sections   -Otime -O2 --diag_remark 1 -I%BREWDIR%\inc
CPP=armcpp -c --littleend --cpu ARMxxxx --apcs /ropi/interwork/norwpi --split_sections -Otime -O2 --diag_remark 1 -I%BREWDIR%\inc
AR=armar --create 
LN=armlink --ropi --rwpi --split --rw_base 0 --strict --entry AEEMod_Load --first AEEMod_Load

...where --cpu AMRxxx is you ARM core, and for elf2mod compile, I've been using:

C=armcc -c --littleend --cpu ARMxxx --apcs=/adsabi --diag_suppress=C3011 --bss_threshold=0 \
     --split_sections   -Otime -O2 --diag_remark 1 -I%BREWDIR%\inc
CPP=armcpp -c --littleend --cpu ARMxxxx --apcs=/adsabi --diag_suppress=C3011 --bss_threshold=0 \
     --split_sections   -Otime -O2 --diag_remark 1 --no_exceptions --no_exceptions_unwind  -I%BREWDIR%\inc
AR=armar --create 
LN=armlink --strict --entry AEEMod_Load --first AEEMod_Load --datacompressor=off --reloc --vfemode=off --ro_base 0 --split --no_exceptions

...which is a mix of suggestions from the ARM site and my own experience. See how with this recipe relocations are put in the ELF file (--reloc) for elf2mod? No special linker script needed either. (Add --thumb if you need it.)

Everything was building great until one of our applets failed the link with


Error: L6218E: Undefined symbol main (referred from kernel.o).
Finished: 0 information, 0 warning and 1 error messages.

But wait, we're not linking any kernel.o! What is going on?

Detective Work

If you search for this error on Google (or Bing, which seems to work better) you will find some folks who ran into this on the BREW forums, and also this tech note on the ARM InfoCenter site.

Both of these indicate that if any of the standard C/C++ runtime library (RTL) is brought into an applet, the new library structure will require a main() function to satisfy __rt_exit(), which satisfies __rt_lib_init().

Now ARM's advice to put in a dummy main() may work well on other platforms, but it won't work on BREW, for reasons I will get to at the end of this article.

But the other standard advice is good. Don't call exit() in your code. Also, don't call memcpy() instead of MEMCPY() and so on. But you're not stupid, and neither am I, so we haven't done any of that, have we? So it must be something else. Time to dump binaries and find the caller...

First I tried to use fromElf --text -s to dump my objects and see who could be calling into the RTL. That was inconclusive.

Next, I decided to try and get a cross-reference list from armlink (--xref). But apparently if you can't link you can't get one!

So, I did put in a dummy main() so I could link, and then --xref works and makes an enormous amount of output I captured in a file. I whittled this down and got to:

wards_applet.o(i._ZN17WardsAppletX...v) refers to aeabi_vec_ctor_nocookie_nodtor.o(i.__aeabi_vec_ctor_nocookie_nodtor) for __aeabi_vec_ctor_nocookie_nodtor

aeabi_vec_ctor_nocookie_nodtor.o
    aeabi_vec_ctor_nocookie_nodtor.o(.ARM.exidx) refers to aeabi_vec_ctor_nocookie_nodtor.o(i.__aeabi_vec_ctor_nocookie_nodtor) for i.__aeabi_vec_ctor_nocookie_nodtor
    aeabi_vec_ctor_nocookie_nodtor.o(.ARM.exidx) refers to unwind_pr0.o(.text) for __aeabi_unwind_cpp_pr0
    unwind_pr0.o(.text) refers to unwind_prcommon.o(.text) for __ARM_unwind_cpp_prcommon
          unwind_prcommon.o(.text) refers to unwinder.o(.text) for _Unwind_VRS_Get
          unwind_prcommon.o(.text) refers to unwind_activity.o(.emb_text) for _Unwind_Activity
              unwinder.o(.emb_text) refers to unwinder.o(.text) for __ARM_Unwind_RaiseException
              unwinder.o(.text) refers to bsearchnoex.o(.text) for bsearch
              unwinder.o(.text) refers to unwind_activity.o(.emb_text) for _Unwind_Activity
              unwinder.o(.text) refers to unwinder.o(.emb_text) for __ARM_Unwind_VRS_corerestore
              unwinder.o(.text) refers to abort.o(.text) for abort
              unwinder.o(.text) refers to h1_alloc.o(.text) for malloc
              unwinder.o(.text) refers to rt_memcpy_w.o(.text) for __aeabi_memcpy4
              unwinder.o(.text) refers to h1_free.o(.text) for free
              unwinder.o(.text) refers (Weak) to unwind_pr0.o(.text) for __aeabi_unwind_cpp_pr0

Ah ha! Something called aeabi_vec_ctor_nocookie_nodtor() is doing exception stuff and bringing in the RTL (even though I have "no exception" switches all over the place on my command lines!). What is this routine?

It is part of the ARM extended application binary interface, that is, it is an ARM variation on (and wrapper around) a standard C++ runtime support routine (__cxa_vec_ctor()). Its purpose is to call all of the constructors in a vector when you make an array of objects. It is also supposed to catch any exceptions -- ahem -- and call destructors on previously constructed objects if an exception is thrown. Uh oh!

Could it be that the nice RTL from ARM is not as BREW compatibile as we would like? Well, sure!

The Solution

To stop this stuff from being linked in, while preserving our ability to have arrays of objects, we need to supply our own version of aebi_vect_ctor_nocookie_nodtor() that is "well-behaved" and doesn't use exceptions. Like this:

#ifndef AEE_SIMULATOR
void * __aeabi_vec_ctor_nocookie_nodtor(    void* user_array,
                                           void* (*constructor)(void*),
                                           size_t element_size,
                                           size_t element_count) 

{
    size_t ii = 0;
    char *ptr = (char*) (user_array);
    if ( constructor != NULL )
        for( ; ii != element_count ; ii++, ptr += element_size )
            constructor( ptr );
    return user_array;
}
#endif

...which I placed at the end of AEEModGen.c (it needs "C" linkage, you could also extern "C" if in C++).

Now, the caveat is you can't throw an exception from the constructors in your object array, but you shouldn't be throwing exceptions from a constructor anyway.

A dummy main() won't work because you are still dragging all this RTL machinery and it won't be able to succesfully initalize in the BREW environemnt.

Conclusion

So the story ends. Plop the above routine in your applet and you'll be OK, and if you run into a similar problem I hope the steps I took here will give you a way to debug and fix it (and post the answer for everyone).

Drop me a line if I've gotten this wrong or need to clarify something. (Like, if there is a switch I don't know about that you throw to make RVCT use a BREW-safe RTL (doh!) In which case, about this article, never mind...).

Good luck with your projects.

Ward Willats
Fullpower, Home of MotionX
Makers of MotionX-GPS and MotionX-GPS-Drive for iPhone

If you'd like to e-mail me, please use <brew@wardco.com>

Back to home page.

Valid XHTML 1.0 Strict