Tech Notes

Tech Note #20020001

Problem linking a previously working program when ported to a new platform. Build uses the make utility. Fails on undefined reference to `main' in function `_start'.

Problem Description:

A program, which is built using the system standard make utility, is ported from one platform, where it builds flawlessly, to another platform where the link fails with the following error:


/usr/lib/crt1.o: In function `_start':
/usr/lib/crt1.o(.text+0x18): undefined reference to `main'

Other offsets in `_start' are possible.

Problem Resolution:

A considerable amount of time was spent searching for this problem on the Web with no satisfactory explanation. What turns out to be a simple problem produced a fair amount of grief.

The original, working build was probably done on a platform that uses GNU make, such that the Makefile was built using the features of same. When linking a program that is built out of several pre-compiled components, it is fairly common to link the whole list of object modules based on the executable rule's dependency list. In GNU make, this list is represented by the variable "$^".

When the switch was made to the new platform, the "make" command points to some make other than GNU. This make has no support for the "$^" variable. Rather it expects the "$(.ALLSRC) variable.

Make invokes the linker using the "$^" variable, which is empty, since the variable is undefined. Consequently, the linker is linking with no input object files. Hence the missing main program.

The solution is simple. Switch the "$^" variable to the "$(.ALLSRC)" variable in the Makefile. For example:


myprog: mod1.o mod2.o
    cc -o $@ $^ $(CFLAGS) $(LIBS)

becomes


myprog: mod1.o mod2.o
    cc -o $@ $(.ALLSRC) $(CFLAGS) $(LIBS)

I suppose it is possible for the converse to happen on a Makefile created for a make that uses "$(.ALLSRC)", when it is run under GNU make, since GNU make doesn't support ""$(.ALLSRC)". So much for compatibility.

If you use autoconf, the following autoconf macro will allow you to use the @MAKEALLSOURCE@ variable in your "Makefile.in":


dnl @synopsis ACX_CHECK_MAKE_TYPE
dnl
dnl This macro will check to see whether the build environment
dnl make command (when the command "make" is typed at the
dnl command prompt) is GNU or some other make.  GNU
dnl thoughtfully supports different ways of doing things in
dnl the makefile so it is important to know, if a working
dnl makefile is to be built.  It will replace @MAKECMDNAME@
dnl with the name of the first make command it finds,
dnl @MAKETYPE@ with "GNU" or "generic" and @MAKEALLSOURCE@
dnl with "$^" or "$(.ALLSRC)", in all output files.  These
dnl three variables can be used to build portable makefiles.
dnl
dnl @author Eric Wilde <ewilde@bsmdevelopment.com>

AC_DEFUN([ACX_CHECK_MAKE_TYPE], [
ac_first_make=""
dnl Search all the common names for the first GNU make we
dnl can find.
AC_MSG_CHECKING([type of make available])
for a in "$MAKE" make gmake gnumake; do
    if test -z "$a"; then continue; fi
    if (sh -c "$a --version 2> /dev/null" \
        | grep GNU 2>&1 > /dev/null); then
        ac_first_make=$a
        break
    fi
done
dnl If there was a GNU version, set the variables.
if test "x$ac_first_make" != "x"; then
    MAKECMDNAME=$ac_first_make
    MAKETYPE="GNU"
    MAKEALLSOURCE="\$^"
fi
dnl If the first command found wasn't make, see if we can
dnl find make and what type it is.
if test "x$ac_first_make" != "xmake"; then
    if (sh -c "make -f xxyyzz -q 2>&1" \
        | grep "not found" 2>&1 > /dev/null); then
        dnl If we can't find "make" and there's no GNU make,
        dnl default to make and hope for the best.
        if test "x$ac_first_make" = "x"; then
            MAKECMDNAME="make"
            MAKETYPE="unknown"
            MAKEALLSOURCE="\$(.ALLSRC)"
        fi
    else
        MAKECMDNAME="make"
        MAKETYPE="generic"
        MAKEALLSOURCE="\$(.ALLSRC)"
    fi
fi
dnl Save the results.
AC_MSG_RESULT([$MAKETYPE $MAKECMDNAME])
AC_SUBST(MAKECMDNAME)
AC_SUBST(MAKETYPE)
AC_SUBST(MAKEALLSOURCE)
])