codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Baking Pi Accessing Assembly functions in C++

Tue Jul 16, 2013 3:13 am

I seem to have run into a snag. I've been trying to write my own OS from scratch using the Baking Pi lesson @ http://www.cl.cam.ac.uk/projects/raspberrypi/ as a base to start with (to get the idea) and I've been successful in being able to access all functions created in assembly up until Screen03 Tutorial. I'm basically trying to separate the very internal things from the rest, by only using assembly for the internals and C++ for the abstracted frameworks to be built on top of the assembly code (For obvious ease of coding reasons).

The way I've been getting this to work able to get access is to of course modify the make file to also compile the C++ code, the "main" method of the assembly calls a cpp_main method in C++, and an assembly.h file contains the signatures of all the assembly functions. I've been able to call DrawLine() from Screen02 just fine but I cannot get DrawCharacter() or DrawString() to work. I get a compile error every time I call it in C++: "undefined reference to 'DrawCharacter'" which tells me either there is some name mangling going on or the arguments of the function aren't recognizing them as a match in the signature. Here's some of my working code:

Assembly's main:

Code: Select all

main:
mov sp,#0x8000
bl cpp_main
loop$:
b loop$
assembly.h file:

Code: Select all

#ifndef _ASSEMBLY_H_ 
#define _ASSEMBLY_H_
#ifdef __cplusplus 
extern "C" {
#endif
void* GetGpioAddress();
void SetGpioFunction(unsigned int gpioRegister, unsigned int function);
void SetGpio(unsigned int gpioRegister, unsigned int value);
void* GetSystemTimerBase();
unsigned long long GetTimeStamp();
void Wait(unsigned int delayInMicroSeconds);
void* GetMailboxBase();
void MailboxWrite(unsigned int value, char channel);
unsigned int MailboxRead(char channel);
struct FrameBufferDescription {
	unsigned int width;
	unsigned int height;
	unsigned int vWidth;
	unsigned int vHeight;
	unsigned int pitch;
	unsigned int bitDepth;
	unsigned int x;
	unsigned int y;
	void* pointer;
	unsigned int size;
};
FrameBufferDescription* InitialiseFrameBuffer(unsigned int width, unsigned int height, unsigned int bitDepth);
unsigned int Random();
short foreColor;
FrameBufferDescription* graphicsAddress;
void SetForeColor(unsigned short color);
void SetGraphicsAddress(FrameBufferDescription* value);
void DrawPixel(unsigned int x, unsigned int y);
void DrawLine(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2);
#ifdef __cplusplus 
}
#endif
#endif
C++ code:

Code: Select all

#include "assembly.h"
void cpp_inner() {	
	FrameBufferDescription* frameHeader = InitialiseFrameBuffer(1024U, 768U, 16U);
	if (frameHeader == 0)
	{
		//error
		SetGpioFunction(16, 1);
		SetGpio(16, 0);
	}
	else
	{
		void* frame = frameHeader->pointer;
		unsigned short* pixel = (unsigned short*)frame;
		SetGraphicsAddress(frameHeader);
		unsigned short color = 0;
		while (true)
		{
			x1 = Random() % 1024;
			y1 = Random() % 768;
			x2 = Random() % 1024;
			y2 = Random() % 768;
			color++;
			SetForeColor(color);
			DrawLine(x1, y1, x2, y2);
		}
	}
}
extern "C" void cpp_main() {
	while (true)
	{
		cpp_inner();
	}
}
This has been working for me so far, however, I added the following code which gives that compile error:
To the assembly.h file:

Code: Select all

long DrawCharacter(char character, unsigned int x, unsigned int y);
long DrawString(char* string, unsigned int length, unsigned int x, unsigned int y);

void* FindTag(unsigned short tagNumber);
I've also tried (so match the return type better):

Code: Select all

struct WidthHeight {
	unsigned int width;
	unsigned int height;
}
WidthHeight DrawCharacter(char character, unsigned int x, unsigned int y);
WidthHeight DrawString(char* string, unsigned int length, unsigned int x, unsigned int y);

void* FindTag(unsigned short tagNumber);
To the C++ code cpp_inner() method:

Code: Select all

FrameBufferDescription* frameHeader = InitialiseFrameBuffer(1024U, 768U, 16U);
	if (frameHeader == 0)
	{
		//error
		SetGpioFunction(16, 1);
		SetGpio(16, 0);
	}
	else
	{
		void* frame = frameHeader->pointer;
		unsigned short* pixel = (unsigned short*)frame;
		SetGraphicsAddress(frameHeader);
		
		DrawCharacter((char)'c', 0, 0);
	}
The one unique thing I can see with this method in assembly has two return values, the width and the height, in which I at first was going to just return a long and separate out the values in C++. Alex Chadwick was gracious enough to include the C++ signature in his files which says "u32x2 DrawCharacter(char character, u32 x, u32 y);" Unfortunately, C++ doesn't have a u32x2 type so I thought to use a long instead. Previous assembly methods have included the u32x2 type as parameters but I've been able to treat that as 2 separate parameters with no issue, however here, C++ cannot return multiple values. So I assume that this is the source of the problem however I have no clue what else to try. I've tried to use the WidthHeight struct I defined above, no luck. I've also tried to use int and a return type and just ignore the second return value and also tried void as a return type, neither yielded positive results. Any ideas on what could be the issue?

jnc100
Posts: 54
Joined: Wed Feb 20, 2013 10:10 pm

Re: Baking Pi Accessing Assembly functions in C++

Wed Jul 17, 2013 6:09 am

The correct thing to do is probably to return the struct, which the arm eabi will interpret as having the two members in r0 and r1. As regards the error, I assume its a linking error you get? If so, you can inspect the respective object files (both the assembler code which defines DrawCharacter and the C++ code which references it) with nm, specifically arm-none-eabi-nm <object file> and then see if DrawCharacter is properly defined/mangled etc.

Regards,
John.

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Thu Jul 18, 2013 2:34 am

Weird, now I'm more confused... I took your advice and checked out the drawing.o file that was generated from the compile and noticed that the new methods from Screen02 to Screen03 weren't there. So I did a 'make clean' (just removes all the created files ,of course) and re-maked it. Now I get a different error that I don't know how its possible I'm getting it, the error is caused by the assembly.h file.

The compile output:

Code: Select all

C:\*directory*>make
arm-none-eabi-gcc -c source/ccode.c -o build/ccode.o
In file included from source/ccode.c:8:0:
source/assembly.h:47:1: error: unknown type name 'FrameBufferDescription'
source/assembly.h:52:1: error: unknown type name 'FrameBufferDescription'
source/assembly.h:54:25: error: unknown type name 'FrameBufferDescription'
source/assembly.h:57:1: error: unknown type name 'WidthHeight'
source/assembly.h:58:1: error: unknown type name 'WidthHeight'
make: *** [build/ccode.o] Error 1
How is it unknown, its defined in that very same file? The assembly.h file:

Code: Select all

#ifndef _ASSEMBLY_H_ 
#define _ASSEMBLY_H_
#ifdef __cplusplus 
/*	extern "C" is required in C++ to indicate these functions are not subject
	to name mangling. It is not legal in C hence the ifdef __cplusplus. */
extern "C" {
#endif
void* GetGpioAddress();
void SetGpioFunction(unsigned int gpioRegister, unsigned int function);
void SetGpio(unsigned int gpioRegister, unsigned int value);
void* GetSystemTimerBase();
unsigned long long GetTimeStamp();
void Wait(unsigned int delayInMicroSeconds);
void* GetMailboxBase();
void MailboxWrite(unsigned int value, char channel);
unsigned int MailboxRead(char channel);
struct FrameBufferDescription {
	unsigned int width;
	unsigned int height;
	unsigned int vWidth;
	unsigned int vHeight;
	unsigned int pitch;
	unsigned int bitDepth;
	unsigned int x;
	unsigned int y;
	void* pointer;
	unsigned int size;
};
FrameBufferDescription* InitialiseFrameBuffer(unsigned int width, unsigned int height, unsigned int bitDepth);
unsigned int Random();
short foreColor;
FrameBufferDescription* graphicsAddress;
void SetForeColor(unsigned short color);
void SetGraphicsAddress(FrameBufferDescription* value);
void DrawPixel(unsigned int x, unsigned int y);
void DrawLine(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2);
struct WidthHeight {
	unsigned int width;
	unsigned int height;
};
WidthHeight DrawCharacter(char character, unsigned int x, unsigned int y);
WidthHeight DrawString(char* string, unsigned int length, unsigned int x, unsigned int y);
void* FindTag(unsigned short tagNumber);
#ifdef __cplusplus 
}
#endif
#endif
I even tried moving the struct definitions outside (before) of the 'extern' portion of the header, thinking that it was confusing that with something that was expecting it to be defined externally (in the assembly code like the rest of the definitions), but I get the same errors. Do I need to have another header file for just the structs and include that header inside this header? I didn't think that's allowed. I'm really confused why this is a problem.
Last edited by codemann8 on Thu Jul 18, 2013 6:38 am, edited 1 time in total.

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Thu Jul 18, 2013 4:49 am

Nevermind, I got it. In addition to this CPP class I'm compiling, there also is a C class as well that I'm not using (blank, no runnable code), however, the assembly.h file was included there as well and we all know how C files like structs (they don't). After commenting out the #include all compiles great.

Although, running DrawCharacter() still doesn't seem to display the character on the screen when called from C++, but when I run the original main.s file on the Baking Pi Screen03 page, it displays no problem....but that I'm afraid I'll have to play with some more.

Curious though, anyone know why I have to 'make clean' this time but other times I didn't? I've been incrementally adding things to my OS and changing the C++ code all the time and just ran make without a clean and it ran fine all this time but instead now it has problems?
Last edited by codemann8 on Thu Jul 18, 2013 6:41 am, edited 1 time in total.

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Thu Jul 18, 2013 6:37 am

Sorry for the triple post...but it seems I'm having the same 'undefined reference' issue but for a different reason this time. I now have one line of code in my C++ file (commented everything else out, notice that I'm not referencing any of my assembly functions):

Code: Select all

char text[6] = "Hello";
and I get the following compile error:

Code: Select all

C:\*directory*>make
arm-none-eabi-as -I source/ source/drawing.s -o build/drawing.o
arm-none-eabi-as -I source/ source/frameBuffer.s -o build/frameBuffer.o
arm-none-eabi-as -I source/ source/gpio.s -o build/gpio.o
arm-none-eabi-as -I source/ source/mailbox.s -o build/mailbox.o
arm-none-eabi-as -I source/ source/main.s -o build/main.o
arm-none-eabi-as -I source/ source/random.s -o build/random.o
arm-none-eabi-as -I source/ source/systemTimer.s -o build/systemTimer.o
arm-none-eabi-as -I source/ source/tags.s -o build/tags.o
arm-none-eabi-gcc -c source/ccode.c -o build/ccode.o
arm-none-eabi-g++ -c source/cppcode.cpp -o build/cppcode.o
arm-none-eabi-ld --no-undefined build/drawing.o build/frameBuffer.o build/gpio.o
 build/mailbox.o build/main.o build/random.o build/systemTimer.o build/tags.o bu
ild/ccode.o build/cppcode.o -Map kernel.map -o build/output.elf -T kernel.ld
`.rodata' referenced in section `.text' of build/cppcode.o: defined in discarded
 section `.rodata' of build/cppcode.o
build/cppcode.o: In function `cpp_inner()':
cppcode.cpp:(.text+0x94): undefined reference to `__cxa_end_cleanup'
make: *** [build/output.elf] Error 1
It would appear that any time I use a string in my C++ code I get this message. I can think of one of two solutions to the problem, but I don't know how to fix it myself using either solutions. One thing I think it could be, is that I'm missing a reference to a library when calling the ld compiler from the command line (I don't know how to add this in). And the second was a suggestion I found on StackOverflow to let g++ do the linking instead of ld, however, I also don't know how to write the command for this.

The makefile:

Code: Select all

# The toolchain to use. arm-none-eabi works, but there does exist 
# arm-bcm2708-linux-gnueabi.
ARMGNU ?= arm-none-eabi

# The intermediate directory for compiled object files.
BUILD = build/

# The directory in which source files are stored.
SOURCE = source/

# The name of the output file to generate.
TARGET = kernel.img

# The name of the assembler listing file to generate.
LIST = kernel.list

# The name of the map file to generate.
MAP = kernel.map

# The name of the linker script to use.
LINKER = kernel.ld

# The names of all object files that must be generated. Deduced from the 
# assembly code files in source.
OBJECTS := $(patsubst $(SOURCE)%.s,$(BUILD)%.o,$(wildcard $(SOURCE)*.s)) \
           $(patsubst $(SOURCE)%.c,$(BUILD)%.o,$(wildcard $(SOURCE)*.c)) \
		   $(patsubst $(SOURCE)%.cpp,$(BUILD)%.o,$(wildcard $(SOURCE)*.cpp))
 
# Rule to make everything.
all: $(TARGET) $(LIST)

# Rule to remake everything. Does not include clean.
rebuild: all

# Rule to make the listing file.
$(LIST) : $(BUILD)output.elf
	$(ARMGNU)-objdump -d $(BUILD)output.elf > $(LIST)

# Rule to make the image file.
$(TARGET) : $(BUILD)output.elf
	$(ARMGNU)-objcopy $(BUILD)output.elf -O binary $(TARGET) 

# Rule to make the elf file.
$(BUILD)output.elf : $(OBJECTS) $(LINKER)
	$(ARMGNU)-ld --no-undefined $(OBJECTS) -Map $(MAP) -o $(BUILD)output.elf -T $(LINKER)

# Rule to make the object files.
$(BUILD)%.o: $(SOURCE)%.s
	$(ARMGNU)-as -I $(SOURCE) $< -o [email protected]
	
# Rule to make the object files.
$(BUILD)%.o: $(SOURCE)%.c
	$(ARMGNU)-gcc -c $< -o [email protected]
	
# Rule to make the object files.
$(BUILD)%.o: $(SOURCE)%.cpp
	$(ARMGNU)-g++ -c $< -o [email protected]

# Rule to clean files.
clean : 
	-rm -f $(BUILD)*.o 
	-rm -f $(BUILD)output.elf
	-rm -f $(TARGET)
	-rm -f $(LIST)
	-rm -f $(MAP)
Any ideas on what to change in my makefile to make this compile, it would be terribly hard to write an OS without the use of strings.
Last edited by codemann8 on Fri Jul 19, 2013 3:24 am, edited 2 times in total.

User avatar
joan
Posts: 14472
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Baking Pi Accessing Assembly functions in C++

Thu Jul 18, 2013 6:56 am

In C terms you seem to be creating structures, not types.

For instance I'd use the following invocation

Code: Select all

typedef struct {
   unsigned int width;
   unsigned int height;
}  WidthHeight_t;

WidthHeight_t DrawCharacter(char character, unsigned int x, unsigned int y);
rather than

Code: Select all

struct WidthHeight {
   unsigned int width;
   unsigned int height;
};
WidthHeight DrawCharacter(char character, unsigned int x, unsigned int y);

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Thu Jul 18, 2013 11:38 pm

But I'm not using C, I'm using C++

User avatar
AndyD
Posts: 2333
Joined: Sat Jan 21, 2012 8:13 am
Location: Melbourne, Australia
Contact: Website

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 2:24 am

codemann8 wrote:But I'm not using C, I'm using C++
Well yes and no

Code: Select all

/*   extern "C" is required in C++ to indicate these functions are not subject
   to name mangling. It is not legal in C hence the ifdef __cplusplus. */
This is valid C and C++

Code: Select all

    typedef struct {
       unsigned int width;
       unsigned int height;
    }  WidthHeight_t;

    WidthHeight_t DrawCharacter(char character, unsigned int x, unsigned int y);
This is valid C++, but not C

Code: Select all

    struct WidthHeight {
       unsigned int width;
       unsigned int height;
    };
    WidthHeight DrawCharacter(char character, unsigned int x, unsigned int y);
This is valid C and C++

Code: Select all

    struct WidthHeight {
       unsigned int width;
       unsigned int height;
    };
    struct WidthHeight DrawCharacter(char character, unsigned int x, unsigned int y);
You have all the code wrapped up in an "extern C" block. So the compiler treats the code as having C linkage. You should use the code in either the first or third block above to make the code consistent with C.
Last edited by AndyD on Fri Jul 19, 2013 4:18 am, edited 1 time in total.

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 3:07 am

Well, I mean this is great and all, but I'm past that issue. It still compiles and works fine the way it is. I mean, I will change it to a typedef struct instead just to be more correct about it. But...

The issue now is that I can't seem to use strings in code. See not my last comment but the one above.

User avatar
AndyD
Posts: 2333
Joined: Sat Jan 21, 2012 8:13 am
Location: Melbourne, Australia
Contact: Website

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 7:19 am

codemann8 wrote:Any ideas on what to change in my makefile to make this compile, it would be terribly hard to write an OS without the use of strings.
If I have read the GNU manual correctly you could replace this

Code: Select all

$(ARMGNU)-ld --no-undefined $(OBJECTS) -Map $(MAP) -o $(BUILD)output.elf -T $(LINKER)
with this

Code: Select all

$(ARMGNU)-g++ $(OBJECTS) -o $(BUILD)output.elf -Xlinker --no-undefined -Xlinker -Map=$(MAP) -T $(LINKER)

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 7:30 pm

Alright, I gave that a try. I got this compile error:

Code: Select all

C:\*directory*>make
arm-none-eabi-as -I source/ source/drawing.s -o build/drawing.o
arm-none-eabi-as -I source/ source/frameBuffer.s -o build/frameBuffer.o
arm-none-eabi-as -I source/ source/gpio.s -o build/gpio.o
arm-none-eabi-as -I source/ source/mailbox.s -o build/mailbox.o
arm-none-eabi-as -I source/ source/main.s -o build/main.o
arm-none-eabi-as -I source/ source/random.s -o build/random.o
arm-none-eabi-as -I source/ source/systemTimer.s -o build/systemTimer.o
arm-none-eabi-as -I source/ source/tags.s -o build/tags.o
arm-none-eabi-gcc -c source/ccode.c -o build/ccode.o
arm-none-eabi-g++ -c source/cppcode.cpp -o build/cppcode.o
arm-none-eabi-g++ build/drawing.o build/frameBuffer.o build/gpio.o build/mailbox
.o build/main.o build/random.o build/systemTimer.o build/tags.o build/ccode.o bu
ild/cppcode.o -o build/output.elf -Xlinker --no-undefined -Xlinker -Map=kernel.m
ap -T kernel.ld
c:/yagarto/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld.e
xe: cannot find crt0.o: No such file or directory
collect2.exe: error: ld returned 1 exit status
make: *** [build/output.elf] Error 1
Googling that error only brought me to several pages saying that this error attributes to the compiler not being able to locate the standard libraries. But, if I'm building a OS from scratch, I shouldn't need (or want) standard libraries; I should only have to build my own. I commented out that 'char text[6] = "Hello";' line of code, and the compiler still barks. I looked back on that GNU manual link you posted and there is a -nostdlib flag, which I added.

Code: Select all

$(ARMGNU)-g++ $(OBJECTS) -o $(BUILD)output.elf -nostdlib -Xlinker --no-undefined -Xlinker -Map=$(MAP) -T $(LINKER)
It made the 'No such file' error go away but made the original 'undefined reference to __cxa_end_cleanup' error come back. So it seems, having g++ compile and link is not solving this one.

User avatar
AndyD
Posts: 2333
Joined: Sat Jan 21, 2012 8:13 am
Location: Melbourne, Australia
Contact: Website

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 11:08 pm

Perhaps look at this page about "Working Without Standard Library". It suggests using "g++ -c -nostdlib -fno-exceptions -fno-rtti a.cpp" when you compile your object files.

codemann8
Posts: 14
Joined: Tue Jul 16, 2013 2:23 am

Re: Baking Pi Accessing Assembly functions in C++

Fri Jul 19, 2013 11:32 pm

Excellent, this made the undefined reference error go away, but there's another error still left:

Code: Select all

C:\*directory*>make
arm-none-eabi-as -I source/ source/drawing.s -o build/drawing.o
arm-none-eabi-as -I source/ source/frameBuffer.s -o build/frameBuffer.o
arm-none-eabi-as -I source/ source/gpio.s -o build/gpio.o
arm-none-eabi-as -I source/ source/mailbox.s -o build/mailbox.o
arm-none-eabi-as -I source/ source/main.s -o build/main.o
arm-none-eabi-as -I source/ source/random.s -o build/random.o
arm-none-eabi-as -I source/ source/systemTimer.s -o build/systemTimer.o
arm-none-eabi-as -I source/ source/tags.s -o build/tags.o
arm-none-eabi-gcc -c source/ccode.c -o build/ccode.o
#arm-none-eabi-g++ -c source/cppcode.cpp -o build/cppcode.o
arm-none-eabi-g++ -c -nostdlib -fno-exceptions -fno-rtti source/cppcode.cpp -o b
uild/cppcode.o
arm-none-eabi-g++ build/drawing.o build/frameBuffer.o build/gpio.o build/mailbox
.o build/main.o build/random.o build/systemTimer.o build/tags.o build/ccode.o bu
ild/cppcode.o -o build/output.elf -nostdlib -Xlinker --no-undefined -Xlinker -Ma
p=kernel.map -T kernel.ld
`.rodata' referenced in section `.text' of build/cppcode.o: defined in discarded
 section `.rodata' of build/cppcode.o
collect2.exe: error: ld returned 1 exit status
make: *** [build/output.elf] Error 1
`.rodata' referenced in section `.text' of build/cppcode.o: defined in discarded
section `.rodata' of build/cppcode.o

As I'm googling this one, it appears in many instances people would refer to this as a bug in the compiler. However, one post out there led me to believe I had to add *(.rodata) to my .text section in my linker file. I did so and it compiled finally. Was this the right thing for me to do? I haven't tested it, and really have no way of doing at this point, to see if this truly solved it.

Linker file after change:

Code: Select all

SECTIONS {
	/*
	* First and formost we need the .init section, containing the code to 
        * be run first. We allow room for the ATAGs and stack and conform to 
        * the bootloader's expectation by putting this code at 0x8000.
	*/
	.init 0x8000 : {
		*(.init)
	}
	
	/* 
	* Next we put the rest of the code.
	*/
	.text : {
		*(.text)
		*(.rodata)
	}
	
	/* 
	* Next we put the data.
	*/
	.data : {
		*(.data)
	}

	/*
	* Finally comes everything else. A fun trick here is to put all other 
	* sections into this section, which will be discarded by default.
	*/
	/DISCARD/ : {
		*(*)
	}
}

jnc100
Posts: 54
Joined: Wed Feb 20, 2013 10:10 pm

Re: Baking Pi Accessing Assembly functions in C++

Sat Jul 20, 2013 9:27 am

codemann8 wrote:Was this the right thing for me to do?
It should work fine on raw binaries on the Pi. To be completely correct, however, you should include it in its own output section (just copy the .data entries as a template and replace with .rodata) as it affects the flags set in the object file. Specifically, .text is marked as read-only, executable, and .rodata is only marked read-only. You should probably do something similar with the .bss section (and zero .bss at the start of your program) otherwise you won't be able to use statically defined variables whose initial value is zero.

There is an example reasonably complete linker script here although you'd want to replace the .text.boot line with .init

Regards,
John.

Return to “Bare metal, Assembly language”