How can I check if a library was compiled with -fno-rtti? - c++

Assume a simple file bla.cpp:
struct MyClass {
virtual int foo(int x);
virtual ~MyClass();
};
int MyClass::foo(int x) { return x + 23; }
MyClass::~MyClass() {}
Build into a shared library with
g++ -c -fPIC bla.cpp
g++ -shared -o bla.so bla.o
will usually contain some type_info symbol because RTTI is enabled by default on gcc. However, if I build with
g++ -c -fPIC -fno-rtti bla.cpp
the type_info will be missing.
Is there a simple, reliable way (on gcc or clang) to check if a library has been built with -fno-rtti or -frtti? I ask because today I stared at the infamous undefined reference to type_info and it took me a moment to understand that this was cause by a library I was linking against being built with -fno-rtti.

If a class has virtual. functions, it should have type info. Do nm -C libname.so and watch for "vtable for", "typeinfo for", and "typeinfo name for". Example:
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .rdata$_ZTI3Foo
00000000 r .rdata$_ZTS3Foo
00000000 r .rdata$_ZTV3Foo
00000000 r .rdata$zzz
00000000 t .text
00000000 T Foo::foo()
00000000 R typeinfo for Foo
00000000 R typeinfo name for Foo
00000000 R vtable for Foo
U vtable for __cxxabiv1::__class_type_info
If you have vtable but not typeinfo, this is compiled with -fno-rtti. Example:
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .rdata$_ZTV3Foo
00000000 r .rdata$zzz
00000000 t .text
00000000 T Foo::foo()
00000000 R vtable for Foo
If you don't have any virtual functions, you cannot tell (and should not care).

If you need this for configuration, do as GNU autoconf does: Write a minimal proggie that does the checking and build that one. Whether the build (or perhaps a run) fails or not tells you what you need to know.

Related

Using dlopen/dlsym to open C++ shared library - dlsym returns NULL

I have not yet dealt with shared libraries in C++, and am having some trouble. I want to create a shared library and then have a C function pick up on that library. So here is my shared library file:
extern int nothing();
//sym.cpp
int nothing() {
return 0;
}
Below is my dlopen/dlsym script:
//symtest.c
#include <stdio.h>
#include <dlfcn.h>
int main(){
void *handle;
handle = dlopen("/path/to/lib/sym.so",RTLD_NOW);
int (*onload)(void *, void **, int);
onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,"nothing");
if(onload==NULL) {
printf("NULL");
}
return 0;
}
Compile and ran as following:
$ g++ -shared -fPIC -o sym.so sym.cpp
$ gcc symtest.c -ldl -o symtest
$ ./symtest
NULL
Why am I getting NULL? I am pretty sure this symbol is getting exported, at least by observing the output of the following commands.
nm:
$ nm -CD sym.so | grep " T "
0000000000000670 T nothing()
000000000000067c T _fini
0000000000000518 T _init
objdump:
$ objdump -T sym.so
sym.so: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000518 l d .init 0000000000000000 .init
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _Jv_RegisterClasses
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
0000000000200970 g D .bss 0000000000000000 Base _end
0000000000200968 g D .got.plt 0000000000000000 Base _edata
0000000000200968 g D .bss 0000000000000000 Base __bss_start
0000000000000518 g DF .init 0000000000000000 Base _init
000000000000067c g DF .fini 0000000000000000 Base _fini
0000000000000670 g DF .text 000000000000000b Base _Z7nothingv
There is a bigger picture here (Creating C++ Redis Module - "does not export RedisModule_OnLoad() symbol") but I looked through some Redis source code to produce a minimalistic example. Anyone have any idea what I am doing wrong here?
As requested, nm without -C option:
$ nm -D sym.so
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
0000000000000670 T _Z7nothingv
0000000000200968 B __bss_start
w __cxa_finalize
w __gmon_start__
0000000000200968 D _edata
0000000000200970 B _end
000000000000067c T _fini
0000000000000518 T _init
C++ has function overloading so there can be multiple functions with the same name and just different parameters.
Since object files store only names, C++ applies what is called name mangling. It adds extra symbols representing email parameters the name to differentiate between different versions.
When using dlsym, one must use the mangled name to get at the function address.
Now since name mangling is platform specific it is often better to use C linkage (no name mangling)
This can be done with the extern "C" declaration.

Can I hide or remove the class name in the shared library?

I find the class name cannot be hidden in the shared library if this class is polymorphic. For example,
// example.cpp
#include <stdio.h>
#include <string.h>
// #define virtual
class Base
{
public:
virtual const char* whatiam()
{
return "Papa";
}
};
class Child : public Base
{
public:
virtual const char* whatiam()
{
return "Son";
}
};
const char* whatiam(Base* obj)
{
return obj->whatiam();
}
__attribute__((visibility("default"))) const char* TheAPI(int n)
{
static char buf[64];
Child t;
sprintf(buf, "I'm %s.", whatiam(&t));
return buf;
}
I build a share library on Linux with gcc like this
$ g++ -fPIC -shared -fvisibility=hidden ../example.cpp -o libexample.so
$ strip -R .comment -R .note libexample.so
Then I open the libexample.so as a normal file in Emacs and search, the class name Base and Child will be find out.
And if I uncomments the statement // #define virtual to be #define virtual, that is to say make Base and Child without virtual methods, I find the class name Base and Child will not be find out in the shared library.
Dose the class name be stored in the class vtable by the compiler? Or some other reasons caused this problem?
I find the class name cannot be hidden in the shared library if this class is polymorphic.
Not clear what kind of hiding you refer to.
From linker symbol visibility perspective all names with internal linkage are hidden. Classes do not have linkage at all, functions and variables do:
$ nm -C libexample.so
nm: libexample.so: no symbols
$ nm -D -C libexample.so
0000000000201030 B __bss_start
w __cxa_finalize
0000000000201030 D _edata
00000000002010a0 B _end
0000000000000944 T _fini
w __gmon_start__
0000000000000728 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U sprintf
0000000000000899 T TheAPI(int)
U vtable for __cxxabiv1::__class_type_info
U vtable for __cxxabiv1::__si_class_type_info
$ strings libexample.so | c++filt
__gmon_start__
_init
_fini
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
__cxa_finalize
_Jv_RegisterClasses
TheAPI(int)
sprintf
vtable for __cxxabiv1::__si_class_type_info
vtable for __cxxabiv1::__class_type_info
libstdc++.so.6
libm.so.6
libgcc_s.so.1
libc.so.6
_edata
__bss_start
_end
CXXABI_1.3
GLIBC_2.2.5
fffff.
Papa
I'm %s.
5Child
4Base
;*3$"
Those strings 5Child and 4Base are typeinfo returned by typeid():
typeinfo name for Child:
.string "5Child"
.hidden typeinfo for Child
.weak typeinfo for Child
.section .data.rel.ro._ZTI5Child,"awG",#progbits,typeinfo for Child,comdat
.align 16
.type typeinfo for Child, #object
.size typeinfo for Child, 24
typeinfo name for Base:
.string "4Base"
.hidden typeinfo for Base
.weak typeinfo for Base
.section .data.rel.ro._ZTI4Base,"awG",#progbits,typeinfo for Base,comdat
.align 16
.type typeinfo for Base, #object
.size typeinfo for Base, 16
You can disable typeinfo with -fno-rtti compiler switch:
-fno-rtti
Disable generation of information about every class with virtual
functions for use by the C++ run-time type identification features
(dynamic_cast and typeid). If you don't use those parts of the
language, you can save some space by using this flag. Note that
exception handling uses the same information, but G++ generates it
as needed. The dynamic_cast operator can still be used for casts
that do not require run-time type information, i.e. casts to "void
*" or to unambiguous base classes.

Symbol not found when using template defined in a library

I'm trying to use the adobe xmp library in an iOS application but I'm getting link errors. I have the appropriate headers and libraries in my path, but I'm getting link errors. I double-checked to make sure the headers and library are on my path. I checked the mangled names of the methods, but they aren't in the library (I checked using the nm command). What am I doing wrong?
Library Header:
#if defined ( TXMP_STRING_TYPE )
#include "TXMPMeta.hpp"
#include "TXMPIterator.hpp"
#include "TXMPUtils.hpp"
typedef class TXMPMeta <TXMP_STRING_TYPE> SXMPMeta; // For client convenience.
typedef class TXMPIterator <TXMP_STRING_TYPE> SXMPIterator;
typedef class TXMPUtils <TXMP_STRING_TYPE> SXMPUtils;
.mm file:
#include <string>
using namespace std;
#define IOS_ENV
#define TXMP_STRING_TYPE string
#import "XMP.hpp"
void DoStuff()
{
SXMPMeta meta;
string returnValue;
meta.SetProperty ( kXMP_NS_PDF, "test", "{ formId: {guid} }" );
meta.DumpObject(DumpToString, &returnValue);
}
Link Errors:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::DumpObject(int (*)(void*, char const*, unsigned int), void*) const", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::TXMPMeta()", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::SetProperty(char const*, char const*, char const*, unsigned int)", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::~TXMPMeta()", referenced from:
(null): Linker command failed with exit code 1 (use -v to see invocation)
Basically what's happened is that you only have the definitions in the headers
if I say
template<class T> T something(T); somewhere, that tells the compiler "trust me bro, it exists, leave it to the linker"
and it adds the symbol to the object file as if it did exist. Because it can see the prototype it knows how much stack space, what type it returns and such, so it just sets it up so the linker can just come along and put the address of the function in.
BUT in your case there is no address. You /MUST/ have the template definition (not just declaration) in the same file so the compiler can create one (with weak linkage) so here it's assumed they exist, but no where does it actually stamp out this class from a template, so the linker doesn't find it, hence the error.
Will fluff out my answer now, hope this helps.
Addendum 1:
template<class T> void output(T&);
int main(int,char**) {
int x = 5;
output(x);
return 0;
}
This will compile but NOT link.
Output:
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -Isrc -c src/main.cpp -o build/main.o
g++ build/main.o -o a.out
build/main.o: In function `main':
(my home)/src/main.cpp:13: undefined reference to `void output<int>(int&)'
collect2: error: ld returned 1 exit status
make: *** [a.out] Error 1
(I hijacked an open projet for this hence the names)
As you can see the compile command works fine (the one that ends in -o build/main.o) because we tell it "look this function exists"
So in the object file it says to the linker (in some "name managled form" to keep the templates) "put the location in memory of void output(int&); here" the linker can't find it.
Compiles and links
#include <iostream>
template<class T> void output(T&);
int main(int,char**) {
int x = 5;
output(x);
return 0;
}
template<class T> void output(T& what) {
std::cout<<what<<"\n";
std::cout.flush();
}
Notice line 2, we tell it "there exists a function, a template in T called output, that returns nothing and takes a T reference", that means it can use it in the main function (remember when it's parsing the main function it hasn't seen the definition of output yet, it has just been told it exists), the linker then fixes that. 'though modern compilers are much much smarter (because we have more ram :) ) and rape the structure of your code, link-time-optimisation does this even more, but this is how it used to work, and how it can be considered to work these days.
Output:
make all
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -Isrc -c src/main.cpp -o build/main.o
g++ build/main.o -o a.out
As you can see it compiled fine and linked fine.
Multiple files without include as proof of this
main.cpp
#include <iostream>
int TrustMeCompilerIExist();
int main(int,char**) {
std::cout<<TrustMeCompilerIExist();
std::cout.flush();
return 0;
}
proof.cpp
int TrustMeCompilerIExist() {
return 5;
}
Compile and link
make all
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -Isrc -c src/main.cpp -o build/main.o
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -MM src/proof.cpp >> build/proof.o.d ; then rm build/proof.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings -Isrc -c src/proof.cpp -o build/proof.o
g++ build/main.o build/proof.o -o a.out
(Outputs 5)
Remember #include LITERALLY dumps a file where it says "#include" (+ some other macros that adjust line numbers) this is called a translation unit. Rather than using a header file to contain "int TrustMeCompilerIExist();" which declares that the function exists (but the compiler again doesn't know where it is, the code inside of it, just that it exists) I repeated myself.
Lets look at proof.o
command
objdump proof.o -t
output
proof.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 proof.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .debug_info 0000000000000000 .debug_info
0000000000000000 l d .debug_abbrev 0000000000000000 .debug_abbrev
0000000000000000 l d .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l d .debug_line 0000000000000000 .debug_line
0000000000000000 l d .debug_str 0000000000000000 .debug_str
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 0000000000000006 _Z21TrustMeCompilerIExistv
Right at the bottom there, there's a function, at offset 6 into the file, with debugging information, (the g is global though) you can see it's called _Z (this is why _ is reserved for some things, I forget what exactly... but it's to do with this) and Z is "integer", 21 is the name length, and after the name, the v is "void" the return type.
The zeros at the start btw are the section number, remember binaries can be HUGE.
Disassembly
running:
objdump proof.o -S gives
proof.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_Z21TrustMeCompilerIExistv>:
int TrustMeCompilerIExist() {
return 5;
}
0: b8 05 00 00 00 mov $0x5,%eax
5: c3 retq
Because I have -g you can see it put the code that the assembly relates to (it makes more sense with bigger functions, it shows you what the following instructions until the next code block actually do) that wouldn't normally be there.
main.o
Here's the symbol table, obtained the same way as the above:
objdump main.o -t
main.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .text.startup 0000000000000000 .text.startup
0000000000000030 l F .text.startup 0000000000000026 _GLOBAL__sub_I_main
0000000000000000 l O .bss 0000000000000001 _ZStL8__ioinit
0000000000000000 l d .init_array 0000000000000000 .init_array
0000000000000000 l d .debug_info 0000000000000000 .debug_info
0000000000000000 l d .debug_abbrev 0000000000000000 .debug_abbrev
0000000000000000 l d .debug_loc 0000000000000000 .debug_loc
0000000000000000 l d .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l d .debug_ranges 0000000000000000 .debug_ranges
0000000000000000 l d .debug_line 0000000000000000 .debug_line
0000000000000000 l d .debug_str 0000000000000000 .debug_str
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text.startup 0000000000000026 main
0000000000000000 *UND* 0000000000000000 _Z21TrustMeCompilerIExistv
0000000000000000 *UND* 0000000000000000 _ZSt4cout
0000000000000000 *UND* 0000000000000000 _ZNSolsEi
0000000000000000 *UND* 0000000000000000 _ZNSo5flushEv
0000000000000000 *UND* 0000000000000000 _ZNSt8ios_base4InitC1Ev
0000000000000000 *UND* 0000000000000000 .hidden __dso_handle
0000000000000000 *UND* 0000000000000000 _ZNSt8ios_base4InitD1Ev
0000000000000000 *UND* 0000000000000000 __cxa_atexit
See how it says undefined, that's because it doesn't know where it is, it just knows it exists (along with the standard lib stuff, which the linker will find itself)
In closing
USE HEADER GUARDS and with templates put #include file.cpp at the bottom BEFORE the closing header guard. that way you can include header files as usual :)
The answer to your question is present in ever sample that comes with XMP SDK Toolkit.Clients must compile XMP.incl_cpp to ensure that all client-side glue code is generated. Do this by including it in exactly one of your source files.
For your ready reference I am pasting below a more detailed explanation present in section Template classes and accessing the API of XMPProgrammersGuide.pdf that comes with XMP SDK Toolkit
Template classes and accessing the API
The full client API is defined and documented in the TXMP*.hpp header files. The TXMP* classes are C++ template classes that must be instantiated with a string class such as std::string, which is used to return text strings for property values, serialized XMP, and so on. To allow your code to access the entire XMP API you must:
Provide a string class such as std::string to instantiate the template classes.
Provide access to XMPCore and XMPFiles by including the necessary defines and headers. To do this, add the necessary define and includes directives to your source code so that all necessary code is incorporated into the build:
#include <string>
#define XMP_INCLUDE_XMPFILES 1 //if using XMPFiles
#define TXMP_STRING_TYPE std::string
#include "XMP.hpp"
The SDK provides complete reference documentation for the template classes, but the templates must be instantiated for use. You can read the header files (TXMPMeta.hpp and so on) for information, but do not include them directly in your code. There is one overall header file, XMP.hpp, which is the only one that C++ clients should include using the #include directive. Read the instructions in this file for instantiating the template classes. When you have done this, the API is available through the concrete classes named SXMP*; that is, SXMPMeta, SXMPUtils, SXMPIterator, and SXMPFiles. This document refers to the SXMP* classes, which you can instantiate and which provide static functions.
Clients must compile XMP.incl_cpp to ensure that all client-side glue code is generated. Do this by including it in exactly one of your source files.
Read XMP_Const.h for detailed information about types and constants for namespace URIs and option flags.

Has anyone an example for wrapping a function in C++?

I have searched online a lot but I couldn't find an example that works with g+, all examples work with GCC.
The error I keep getting is:
wrap_malloc.o: In function `__wrap_malloc(unsigned int)':
wrap_malloc.cc:(.text+0x20): undefined reference to `__real_malloc(unsigned int)'
wrap_malloc.o: In function `main':
wrap_malloc.cc:(.text+0x37): undefined reference to `__wrap_malloc'
collect2: ld returned 1 exit status
The code that creates this error is the following (this code works if I compile it with GCC and change the headers from cstdio to stdio.h):
#include <cstdio>
#include <cstdlib>
void *__real_malloc(size_t);
void *__wrap_malloc(size_t c) {
printf("My malloc called with %d\n", c);
return __real_malloc(c);
}
int main(void) {
void *ptr = malloc(12);
free(ptr);
return 0;
}
This is how I compile it:
wrap_malloc.o: wrap_malloc.cc
g++ -c wrap_malloc.cc -o wrap_malloc.o
wrap_malloc: wrap_malloc.o
g++ wrap_malloc.o -o wrap_malloc -Wl,--wrap,malloc
When you use a C++ compiler, all names are mangled. What this means becomes clear when you run nm wrap_malloc.o, which should give you something like this:
00000000 b .bss
00000000 d .data
00000000 r .rdata
00000000 t .text
U __Z13__real_mallocj
00000000 T __Z13__wrap_mallocj
U _printf
This means that you use (U) a symbol called __Z13__real_mallocj and that you define a symbol in the text segment (T) called __Z13__wrap_mallocj. But you probably want a symbol called __real_malloc. To achieve this you have to say the compiler that __real_malloc is a C-style function, like this:
extern "C" void *__real_malloc(size_t);
extern "C" void *__wrap_malloc(size_t c) {
printf("My malloc called with %d\n", c);
return __real_malloc(c);
}
Now the output of nm is:
00000000 b .bss
00000000 d .data
00000000 r .rdata
00000000 t .text
U ___real_malloc
00000000 T ___wrap_malloc
U _printf
You can see that the name _printf hasn't changed. This is because in the header files, many functions are declared as extern "C" already.
Note: I did all of the above on Windows in the cygwin environment. That's why there is an additional leading underscore in the external symbols.
If this is the complete code, the problem you are having is that you haven't implemented __real_malloc()!
And by the way, identifiers with double-underscores are reserved by the language. You might want to think about picking different names.

C++ Newbie in Linker Hell

Using g++ and having linker errors. I have a simple program in split into two modules: main.cpp and Dice.h Dice.cpp.
main.cpp:
#include <iostream>
#include "Dice.h"
int main(int argc, char **argv) {
int dieRoll = Dice::roll(6);
std::cout<<dieRoll<<std::endl;
std::cin.get();
return 0;
}
Dice.h:
#ifndef DieH
#define DieH
namespace Dice
{
int roll(unsigned int dieSize);
}
#endif
Dice.cpp:
#include <ctime>
#include <cstdlib>
#include "Dice.h"
namespace Dice
{
int roll(unsigned int dieSize)
{
if (dieSize == 0)
{
return 0;
}
srand((unsigned)time(0));
int random_int = 0;
random_int = rand()%dieSize+1;
return random_int;
}
}
I compile and link these files using g++ as follows:
g++ -o program main.cpp Dice.cpp
And I get the following linker error:
Undefined symbols:
"Dice::roll(int)", referenced from:
_main in ccYArhzP.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
I'm completely flummoxed. Any help would be greatly appreciated.
Your code is well-formed.
Ensure that your don't have conflicting file names, the files exist and contain what you think they do. For example, perhaps you have a Dice.cpp that's empty, and you're editing a newly created one somewhere else.
Minimize possible discrepancy by removing unnecessary files; only have main.cpp, dice.h, and dice.cpp.
Your errors do not match your code: "Dice::roll(int)". Observe that this is looking for an int, but your functions take an unsigned int. Make sure your header matches.
Try the following:
g++ main.cpp -c
This will generate main.o, the compiled but not-linked code for main. Do the same with dice.cpp:
g++ dice.cpp -c
You now have two object files that need to be linked together. Do so with:
g++ main.o dice.o
And see if that works. If not, do the following:
nm main.o dice.o
This will list all the available symbols in an object, and should give you something like this:
main.o:
00000000 b .bss
00000000 d .ctors
00000000 d .data
00000000 r .eh_frame
00000000 t .text
00000098 t __GLOBAL__I_main
00000069 t __Z41__static_initialization_and_destruction_0ii
U __ZN4Dice4rollEj
U __ZNSi3getEv
U __ZNSolsEPFRSoS_E
U __ZNSolsEi
U __ZNSt8ios_base4InitC1Ev
U __ZNSt8ios_base4InitD1Ev
U __ZSt3cin
U __ZSt4cout
U __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000 b __ZStL8__ioinit
U ___gxx_personality_v0
U ___main
00000055 t ___tcf_0
U _atexit
00000000 T _main
dice.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 T __ZN4Dice4rollEj
U _rand
U _srand
U _time
C++ mangles function names, which is why everything looks so weird. (Note, there is no standard way of mangling names, this is how GCC 4.4 does it).
Observe that dice.o and main.o refer to the same symbol: __ZN4Dice4rollEj. If these do not match, that's your problem. For example, if I change part of dice.cpp to be this:
// Note, it is an int, not unsigned int
int roll(int dieSize)
Then nm main.o dice.o produces the following:
main.o:
00000000 b .bss
00000000 d .ctors
00000000 d .data
00000000 r .eh_frame
00000000 t .text
00000098 t __GLOBAL__I_main
00000069 t __Z41__static_initialization_and_destruction_0ii
U __ZN4Dice4rollEj
U __ZNSi3getEv
U __ZNSolsEPFRSoS_E
U __ZNSolsEi
U __ZNSt8ios_base4InitC1Ev
U __ZNSt8ios_base4InitD1Ev
U __ZSt3cin
U __ZSt4cout
U __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
00000000 b __ZStL8__ioinit
U ___gxx_personality_v0
U ___main
00000055 t ___tcf_0
U _atexit
00000000 T _main
dice.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 T __ZN4Dice4rollEi
U _rand
U _srand
U _time
Note, this gives two different symbols. main.o looking for this: __ZN4Dice4rollEj and dice.o containing this __ZN4Dice4rollEi. (The last letter differs).
When trying to compile these mismatched symbols (with g++ main.o dice.o), I get:
undefined reference to `Dice::roll(unsigned int)'
Your problem is that you're calling Dice::roll(int) and you wrote a Dice::roll(unsigned int) function. The function it's looking for doesn't actually exist (internally, argument types of a function matter just as much as its name; this is how overloading works). Try passing it an unsigned int (6u for example) and see if that works. Alternately, your header file may not match in both files, and main.cpp thinks your function takes a (signed) int.
I know you don't want to hear anything like it, but "It works for me!".
Your errors look suspiciously old - what version of GCC are you using? This look like GCC 2.x!
Also, try to type dieRoll in main.cpp as unsigned, that might resolve namespace problem on this old compiler.