autc04 / Retro68

a gcc-based cross-compiler for classic 68K and PPC Macintoshes
GNU General Public License v3.0
548 stars 54 forks source link

Building for BeOS PowerPC #140

Open memsom opened 3 years ago

memsom commented 3 years ago

Hey, BeOS PowerPC uses PEF and apps built with (for example) CodeWarrior on the Mac that link against the BeOS shared libraries will run under BeOS. I was wondering how easy it would be to adapt Retro68 to build without the assumed Mac elements? I did build the gcc and binutils a while back, but the compiler seemed to require libretro, which is obviously not really needed. I guess what I am asking is, can a pure PEF exe be built at the moment without any specific Mac dependencies and Retro68 additions like libretro?

autc04 commented 3 years ago

I'm excited about the idea of a BeOS port. I have one old Mac capable of running BeOS/PPC, though I've been too busy with other things (= lazy) to do anything about it.

Pure PEF exes should be possible with -nodefaultlibs. (Or maybe also -nostartfiles).

I'm using a bit of a non-standard setup so I don't have to deal with GCC's build system too much. For "normal" ports of GCC, the platform-specific application entrypoint (__start) is defined somewhere incrtbegin.S in the gcc tree, and the platform-specific parts of the C library are right there in the newlib directory inside gcc. For Retro68, I chose to have both parts separately in libretro so that I can use a nice CMake-based setup.

One challenge for the BeOS port will how we can get GCC (and libstdc++) to work with BeOS's standard C library...

(and a challenge for later: all BeOS's C++ APIs use an entirely GCC-incompatible name mangling scheme; we'll need to get creative in order to access them).

memsom commented 3 years ago

That's great info! I think I still have the compiler tree built on my Catalina partition, so I will try those options. I really want to get this working on my G4 Mac Mini, as that also has a working version of CodeWarrior that builds PEF exes, so I can do some comparisons etc.

BeOS does export standard PEF symbols from its libraries and I did manage to get some C++ working a long time ago with Fred Fish's gcc port for BeOS PowerPC (it did similar to what you have done, but was a lot less advanced, and is quite hard to track down now.) The C++ I used was shimmed though, but it did work. Also - most of the libroot (main base runtime) is in C, not C++. It's mainly when you get to layer above that it starts to become C++. We might get away with it working mostly for non UI apps with a few shims.

memsom commented 2 years ago

I got some more time for this, so I am building Retro68 on my macbook again (old copy was lost in a hard drive upgrade at some point)

Those switches are for building with gcc, right? Not for the build script?

autc04 commented 2 years ago

Yes. When called with these switches, the Retro68 gcc should skip all the mac-specific libraries.

memsom commented 2 years ago

So I have it built on my Macbook Pro and I have copied over the BeOS libs I would need (all are PEF libraries or MW format object files)... what might cause an issue is the startup/teardown code that BeOS uses. It look like this is a couple of MW object files... might need some work to make this in to something GCC can use. I have a tool that I was working on to pull out the raw binary parts, so I will look at how likely it is I can pull out the startup code. If not, I think the Metrowerks tools can disassemble it. What format does GCC use for inline assembler on PowerPC?

Edit: The other possibility is building the tools on a PowerPC Mac, Windows box or under Haiku. I know there are versions of the Metrowerks linker that run on those platforms. I also might be able to convert the object files to XCOFF with that linker...

I'll try to see if I can make the original code work with Retro68 first though.

autc04 commented 2 years ago

I'm guessing that the first step would be to get something without setup code to work, i.e. a program that defines __start (or was it _start?), or a shared library that exports a function.

The startup code for PowerPC Macs only does setup for our own C++ objects, nothing is required by the OS (libretro/ppcstart.c). And there is no setup code for shared libraries. I haven't verified, but I'd be surprised if BeOS requires more.

I'm guessing one could try the following things:

memsom commented 2 years ago

The Metrowerks linker (mwld) can accept XCOFF libs, so I think we can probably do this quite easily. Fred Fish had a port of GCC for BeOS PowerPC back in the day, and that built an XCOFF library and used mwld to do the linking. A long time ago I played with it and got as far as making a C++ program run by hand crafting a header with the mangled names. So I know it is possible. Any version of the PEF compatible MW compiler tool chain will also build valid BeOS exe's, so I have gotten them from both a Windows box and a PowerPC Mac before. There is a cross compiler for BeOS that is known to run under Haiku, so I wonder if I can build Retro68 under Haiku also and have it all in one place. I tried on my MacMini running Tiger, but it failed and I just jumped back to my MacBook as I know it worked last time.

I will see what I can get working.

Thanks for the __start tip. I think BeOS sets up a load of stuff from what I can remember... but most of it is Metrowerks related IIRC. I know that there is a version of the glue lib that has no initialisation, and you use that with the startup code. lib

autc04 commented 2 years ago

a) building Retro68 under Haiku ... I don't see why not. Just your usual tedious let's-pick-a-fight-with-autoconf porting effort. In theory, Retro68 and all its dependencies should work out-of-the-box, in practice, expect some hassle.

b) I had a look at the llibraries in /boot/develop/lib/ppc, and I am now 90% sure that everything they do is about supporting the Metrowerks compiler or its standard libraries. We might need to study start_dyn.o to get command line arguments and exit codes right, but otherwise they can be ignored.

c) There are probably fewer moving parts when going the GCC -> GNU LD -> MakePEF route. Not saying that mwld is bad, but there might be extra compatibility problems on that route.

memsom commented 2 years ago

So - I have just run a little test:

 void __start() {}

I then compiled: powerpc-apple-macos-gcc -nodefaultlibs -nostartfiles start.c -o start which ran and made a start executable (I assume ELF/XOFF?) which I then ran MakePEF start -o startpef and transferred that over to BeOS. I ran pefdump startpef and it looks like it was recognised as a PEF executable. I then ran startpef and it did nothing, but more importantly, did not crash! So.. it sort of works so far.

Edit: okay - I changed the code to this:

void __start()
{
  int x = 0;
  int y = 1;
  int z = x + y;
}

It all compiled okay. After transferring, I did the pefdump again and the exe definitely has more code in it. I'm no PowerPC assembly expert, but the code looks like it might be reasonable:

80000034:  83e1fffc  lwz       r31, 0xfffffffc (r1)
80000038:  4e800020  bclr      20, 0
8000003c:  00000000
80000040:  00002060
80000044:  80010001  lwz       r0, 0x0001 (r1)
80000048:  0000003c
8000004c:  00075f5f
80000050:  73746172  andi.     r20, r27, 0x6172
80000054:  741f0000  andis.    r31, r0, 0x0000

I think it was just the last two lines originally.

The footer of the dump says:

section type DATA:
starts at 80001000, size 10
80001000: 80000000 80001010  00000000 00000000  '................'

section type LOADER:
 entry point descriptor:    80001000
 init routine descriptor:   NONE
 term routine descriptor:   NONE
 import symbol table:       EMPTY
 export symbol table:       EMPTY

So I guess that code is running? The debugger under BeOS is graphical I believe so as I am telnetting and typing in I don't think I can step through he code from here.

memsom commented 2 years ago

I tried to run MakeImport on libroot.so, which is the main shared object in BeOS and I believe what contains most of the C library... but it complains about there being no 'cfrg'... is that something we can work around? I have attached libroot.so in case you have any ideas..

libroot.so.zip

autc04 commented 2 years ago

It does look good. The disassembly you posted seems to be just the tail end of the __start function -- restores the saved value of the register r31 (lwz) and returns from the function (bclr). The rest is padding and ohter stuff, no idea without analyzing more, really.

As for libroot, should be fixable. Mac shared libraries consist of one or more PEF files concatenated together, with a cfrg resource added to tell them apart again. I'm assuming that BeOS uses simple PEF files without those extra shenanigans, ao all that should be needed is to rearrange/circumvent some code in MakeImport.cc. I'll get back to you.

autc04 commented 2 years ago

I have pushed a new branch beos. MakeImport now understands an extra option, --no-cfrg.

So you can try.. MakeImport libroot.so liblibroot.so.o --no-cfrg

Why liblibroot.so.o? There's logic somewhere in MakePEF that strips a lib from the beginning and a .o from the end of the library to be imported (because for MacOS, we want to import just "InterfacLib"). So the resulting PEF should end up importing libroot.so, and I am guessing this is what BeOS wants.

memsom commented 2 years ago

I think that is correct. I can maybe make a simple hello world that links just to libroot.so and upload the zip. I'm pretty sure BeOS just uses the standard PEF linking because the Metrowerks tools for MacOS will build valid BeOS PowerPC exes if you swap out the BeOS headers and libs.

The next step to to try and work out what parts of the glue logic are needed to create command line apps. I guess it might be to do with loading the shared objects, which would be problematic. I'm hoping to get something with the simpler C functions like puts.

memsom commented 2 years ago

Okay I built the beos branch and the MakeImport seems to work with a caveat. I ran:

MakeImport libroot.so libroot.so.o --no-cfrg

And this made a valid file that I think linked with the same code as above:

powerpc-apple-macos-gcc -nodefaultlibs -nostartfiles start.c libroot.so.o -o start MakePEF start startpef

I then ftp'd these to BeOS and tried to run the new binary. I got an error:

sh: ./startpef: Missing library

So that was a bit weird as it all compiled. So I dumped the PEF exe and saw this:

section type LOADER:
 entry point descriptor:    80001000
 init routine descriptor:   NONE
 term routine descriptor:   NONE
 import symbol table:       
  from file root.so:
    class                                  name
 export symbol table:       EMPTY

So, that was then immediately obvious - it looks like the process is stripping off the lib prefix. I tested by making a symbolic link to libroot.so with the name root.so, and yes the exe ran. So I re-ran the MakeImport and gcc steps above naming the output as liblibroot.so.o and the exe when transferred back to BeOS ran without the error (and I also removed the symbolic link to be doubly sure.) So, yeah, I think the BeOS library loader expects the lib prefix to be there.

But the good news is, libroot.so is listed in the imported libraries for the generated PEF. The new library import looks like this:

section type LOADER:
 entry point descriptor:    80001000
 init routine descriptor:   NONE
 term routine descriptor:   NONE
 import symbol table:       
  from file libroot.so:
    class                                  name
 export symbol table:       EMPTY

I made a little test app, which is basically a main function that calls puts("hello world") and then returns 0 and exists, and I have attached the exe (test), the pefdump (test.txt) and also the output from the second working exe built with retro68 (out2.txt.) The dump of the test exe shows that we are not really initialising any of the stuff from libroot as it imports a bunch of stuff. I guess that is the next task - work out how to make the parts it imports work test-and-dumps.zip .

memsom commented 2 years ago

Okay - this is possibly a good sign....

int puts(const char* s);

void __start()
{
  puts("hello");
}

compiled and made PEF as above, linking in libroot.so. I wasn't expecting much. But I ran it on BeOS and...

$ startpef
hello
$ _

So... it worked and it linked to libroot!!

The library section now reads:

section type LOADER:
 entry point descriptor:    80001000
 init routine descriptor:   NONE
 term routine descriptor:   NONE
 import symbol table:
  from file libroot.so:
    class                                  name
    TVECT                                  puts
 export symbol table:       EMPTY
autc04 commented 2 years ago

That's great!

There are so many things one could do based on this....

... and that's just what I could think of at the speed of typing. Anything you're particularly interested in? (not necessarily from the list)

memsom commented 2 years ago

Actually, everything you said, but getting main/startup and C headers (some of the BeAPI is plain C) working would be a great start.

mmuman commented 2 years ago

Reminds me I started fixing GNU LD to actually handle PEF instead of just pretending, if anyone want to have a look at the patch… One more thing I never published :upside_down_face:

mmuman commented 2 years ago

Although I didn't even check if they possibly dropped it upstream already, they have a tendency to remove things they feel nobody uses… For the record, here my last attempt, the goal was to just have enough support to get the Haiku loader to boot from the BeBox.

memsom commented 2 years ago

So, I have been playing a little - it seems like BeOS does something like this on start up:

There are at least 2 routines that are exported from libroot.so called "_call_initroutines" and another called "_thread_do_exit_notifications". We need those to be called to do a basic clean start I think.

I think BeOS also has a couple of DATA symbols exported - "argv_save" and "environ" (both seem to be char **.) I am having trouble with these...

changing start to "void start(int argc, char argv, char envp)" I think we can then so something like this:


extern int _call_init_routines_();
extern void _thread_do_exit_notifications();
extern void exit(int);

int puts(const char *s);

void __start(int argc, char **argv, char **envp)
{
  int result = 0;
  _call_init_routines_();

  puts("hello");

  if(argc > 0)
  {
    puts("there are multiple args");
  }

  for (int i = 0; i < argc; i++)
  {
    puts(argv[i]);
  }

  _thread_do_exit_notifications();

  exit(result);
}

Running this I get:

hello
there are multiple args
./startpef

and if I run the app again some test text after the exe I get:

startpef this is a test

hello
there are multiple args
./startpef
this
is
a
test

However, if I try to use the environ and argv_save symbols, they are not being resolved. I think I am meant to sae the argv there and also save the envp. I'm not 100% sure, but that seems to be what I deduced.

I tried adding a call for main() but for some reason, when I added a new C file with the main in it, it couldn't resolve the symbol and wanted it to be called __main(). I don't know enough about how the symbols are exported from the gcc compiled code to know if that is an issue yet.

autc04 commented 2 years ago

However, if I try to use the environ and argv_save symbols, they are not being resolved. I think I am meant to sae the argv there and also save the envp. I'm not 100% sure, but that seems to be what I deduced.

Is that a link error or does it happen when you try to run it on BeOS?

I tried adding a call for main() but for some reason, when I added a new C file with the main in it, it couldn't resolve the symbol and wanted it to be called __main(). I don't know enough about how the symbols are exported from the gcc compiled code to know if that is an issue yet.

Very puzzling. Can you post error messages?

memsom commented 2 years ago

Is that a link error or does it happen when you try to run it on BeOS?

It linked but running under the terminal in BeOS the app errored stating a symbol wasn't resolved but no extra info. But I got an error dialog saying much more specific info when I ran from the desktop.

Very puzzling. Can you post error messages?

Might be my error. It just had an error that __main couldn't be found by the linker.

memsom commented 2 years ago

Just to avoid confusion - I have started a BeOS PowerPC archival and porting project (beosppc) and I have forked Retro68 under that project so I can make changes and more easily give you pull requests. I thought that was easier than you needing to make me a contributor. I will keep the fork up to date with master and will branch your beos branch and do any work in that branch for now. Let me know if you would prefer to work in some other way and I'm happy to do that.

memsom commented 2 years ago

… For the record, here my last attempt, the goal was to just have enough support to get the Haiku loader to boot from the BeBox.

Okay - I will look at getting this in to a branch and seeing how far it gets us. How close is it to working? I mean - can we build a PEF exe with it?

autc04 commented 2 years ago

"bfd/pef: add naive PEF output (no import/export/reloc/syms)" sounds like it needs more work, we need import & reloc for a working app.

mmuman commented 2 years ago

Btw, the Be Newsletter does mention __start quite briefly

Okay - I will look at getting this in to a branch and seeing how far it gets us. How close is it to working? I mean - can we build a PEF exe with it?

I don't really remember, but as mentioned in the log if it ever worked it was for the loader binary which doesn't need libs or anything.

memsom commented 2 years ago

So a little progress. I have cleaned up my code (attached) and made a makefile. I had to remove the -nodefaultlibs but it now compiles and seems to work under BeOS with a main function.

Welcome to the BeOS shell.

$ cd compiler
$ start
hello this is a working app

If I do and combination of switched with -nodefaults I get a __main not defined error...

$ make pef
powerpc-apple-macos-gcc -o start.xcoff start.o app.o liblibroot.so.o -nodefaultlibs  
/usr/local/retro68/bin/../lib/gcc/powerpc-apple-macos/9.1.0/../../../../powerpc-apple-macos/bin/ld: app.o:app.c:(.pr+0x1c): undefined reference to `.__main'
collect2: error: ld returned 1 exit status
make: *** [start.xcoff] Error 1

Archive.zip

memsom commented 2 years ago

Btw, the Be Newsletter does mention __start quite briefly

That is interesting. I did look through he Be Newsletter archive, but I missed that one. I think that stuff isn't used for plain C, but also from what I can tell, the hooks I am calling seem to be coming from libroot.so...

I don't know if my last attempt built with the code I had for startup or with the stuff in libretro though. I need some more time to test, but that will have to wait for another day unfortunately.

memsom commented 2 years ago

Okay sorry for the delay - I altered the __start function to do a puts, and I am seeing the message I added being shown on the screen. Also, the symbol table in the PEF looks to all be BeOS and from libroot except one function called aexit, so I think pure BeOS code is being run and it isn’t seemingly pulling in the libretro code so far!

I think next thing to try is grabbing some headers from BeOS and seeing how much of C works.

memsom commented 2 years ago

Okay - I have updated the compiler config slightly.

I added the #include <stdio.h> to my app.c and then used printf(...) instead of puts passing 1234 and using %i in the string. That worked. But I think that was using the GCC headers, because I googled some more and I firstly forgot to add the $(INCLUDE) macro to the $(CC)'s in the make file and secondly it seemed to "easy". But it did work under BeOS.

So google found me the solution, adding --nostdincs... which broke the compilation and pointed me to my other mistakes. Adding the following to the INCLUDES variable -I . -I./headers/posix got me an error about not finding BeBuild.h - which is good, because that means it is using the Be headrs! I added -I ./headers/be and it compiled... it seems to work!

$ start
we are in beos land

1234 - hello this is a working app
$
section type LOADER:
 entry point descriptor:    80001030
 init routine descriptor:   NONE
 term routine descriptor:   NONE
 import symbol table:
  from file libroot.so:
    class                                  name
    TVECT                                printf
    TVECT                                  puts
    TVECT          _thread_do_exit_notification
    TVECT                  _call_init_routines_
    TVECT                                atexit
    TVECT                                  exit
 export symbol table:       EMPTY

I will try a BeOS specific C API next.

@autc04 I sent you an email regarding making a BeOS target.... just in case I hit your spam filter again, I thought I'd mention it here.

memsom commented 2 years ago

I tried to call a function in another library (libbe.so) and that wasn't very successful so far. It is still a BeOS C API, but I guess it might be name mangled. I will have another look when I get a chance. The linker couldn't find the function. I did create the import lib for libbe.so and if I don't call any functions the app links and pefdump says the library was linked in the import list.

I was trying to find something simple, so I chose "beep()" as it is parameterless. I will look for something in libroot instead.

mmuman commented 2 years ago

At least on Haiku it's exposed as a C++ function as per headers/os/support/Beep.h so yes, it's exported as beep__Fv there with the gcc mangling scheme.

memsom commented 2 years ago

At least on Haiku it's exposed as a C++ function as per headers/os/support/Beep.h so yes, it's exported as beep__Fv there with the gcc mangling scheme.

Yes, I now remember the issue. A lot of C functions are not declared without name mangling. So yeah - the function is named with mangling. I'll have a look at ways round it next. I don't want to change the header, so I think possibly creating a tool to read the mangled function and generate a proxy might need to happen. As the BeApi is fixed in time I guess I could just generate another lib that wraps the functions - they aren't going to change after all.

mmuman commented 2 years ago

Maybe have a look at find_directory, there are C versions of it.

memsom commented 2 years ago

Okay - I write a little stub function:

#include <Beep.h>

status_t beep__Fv();

status_t beep()
{
  return beep__Fv();
}

And I now get the system beep when I run the app!

So the background task is - do we want to manually write stubs for all functions, or do I want to make the MakeImports app some how take an exported file from the headers that remap the symbols in some way (or write stubs).

memsom commented 2 years ago

So another stab at answering this:

  • see if gcc is happy with any of Be's header files for the C library

Yes it is more or less happy - the main caveat is that for some reason some Be C functions are name mangled... so I think I will need to work out a way to either generate the stubs needed to make those symbols available, or alternatively hand write wrappers (slowly) for each one. The latter is probably something that can be done initially to get things moving.

  • see if we can use Haiku's header files but Link to Be's library (for copyright purity)

So - this might work. They should be compatible. I will test it at some point. But I don't know if certain things won't work because they are changed for Haiku, new for Haiku )most of the networking I assume) or have no PowerPC defines where there should be some.

Be actually gave away a cross complier and I thing that might have included the headers - if not the sdk was definitely downloadable at one point. I think we could probably get away with it - I could ask ACCESS (their lawyer popped up a lot when YellowTab were being put out of the "selling BeOS" business.)

  • change MakeImport to not strip the lib id a --beos or --no-change-name option is passed

I started this and added a '--beos' flag. It wasn't too hard, it was (I think) just excluding where it clips off the "lib" fromt he name of the library.

  • write a proper __start function (dealing with command line parameters and exit codes)

This is half done - I need to get it in to source control.

  • and calling C++ constructors/destructors as on macOS
  • call C++ constructors/destructors from PEF init/term routines instead, that way it might even work in shared libraries (this will be a nice thing for MacOS as well, I guess)

Because the name mangling is breaking stuff - I think I'm holding off on anything C++ related till I get all the C up and running.

My current plan is to write a very simple wrapper scheme that implements stub classes that call the underlying name mangled methods by name (a bit like this probably https://discuss.haiku-os.org/t/current-status-of-language-bindings/11904/10 or the BePascal wrapper.)

  • add a powerpc-be-beos configuration to GCC so we can set different default library paths and don't need to use a macos compiler to compile for BeOS ;-)

I think this is part done from when BeOS was a gcc target.

  • setup libretro to build a BeOS version with the right start file and without mac-specific system call implementations (or put the right start files into GCC itself)

I think this will be how I get __start included by default.

  • make GCC use Be's C standard library and not the newlib C library that is included for MacOS

This is just really linking libroot.so from what I can tell so far.

  • Make libstdc++ use Be's C standard library so we can have modern C++ for command line programs on BeOS
  • Figure out how to use the Be API from plain C (+assembly??)

I think see above.

  • Automatically generate compatibility wrappers to call the Be API from GCC-Compiled C++.

I think this will be by hand to begin with because some system functions are exported with mangled names, so it will need to happen even for some C functions.

mmuman commented 2 years ago

Yes it is more or less happy - the main caveat is that for some reason some Be C functions are name mangled... so I think I will need to work out a way to either generate the stubs needed to make those symbols available, or alternatively hand write wrappers (slowly) for each one. The latter is probably something that can be done initially to get things moving.

That's just because it's a C++ API, they are written as C++ functions and meant to be called from C++ code only. Those meant for C calls have the proper #ifdef guard and extern "C" around them.

So - this might work. They should be compatible. I will test it at some point. But I don't know if certain things won't work because they are changed for Haiku, new for Haiku )most of the networking I assume) or have no PowerPC defines where there should be some.

Many things were patched or done differently, first we don't have the IMP/EXP macros everywhere that were used for declaring exports, if you want to call the BeOS libs you should really use the BeOS headers. What's wrong with them? If you're going to ship libbe & friends anyway…

Be actually gave away a cross complier and I thing that might have included the headers - if not the sdk was definitely downloadable at one point. I think we could probably get away with it - I could ask ACCESS (their lawyer popped up a lot when YellowTab were being put out of the "selling BeOS" business.)

There maybe? Ah no it's missing headers… Wasn't it included on the install CD?

  • write a proper __start function (dealing with command line parameters and exit codes)

This is half done - I need to get it in to source control.

Again this was also done already the crt files that shipped with the SDK, so you could disassemble them to see what they do.

  • and calling C++ constructors/destructors as on macOS
  • call C++ constructors/destructors from PEF init/term routines instead, that way it might even work in shared libraries (this will be a nice thing for MacOS as well, I guess)

Because the name mangling is breaking stuff - I think I'm holding off on anything C++ related till I get all the C up and running.

Hmm can't GCC be told to use the MW mangling in some way? LLVM/clang would be probably cleaner on this aspect though…

  • add a powerpc-be-beos configuration to GCC so we can set different default library paths and don't need to use a macos compiler to compile for BeOS ;-)

I think this is part done from when BeOS was a gcc target.

Be definitely published the GCC source code they used anyway so you can always look at that.

This is just really linking libroot.so from what I can tell so far.

libroot includes both libc and libm on BeOS.

memsom commented 2 years ago

That's just because it's a C++ API, they are written as C++ functions and meant to be called from C++ code only. Those meant for C calls have the proper #ifdef guard and extern "C" around them.

Yeah - this does seem like an oversight though. I don't think not mangling the names would have made any difference. It is more like the developers didn't realise till much later and then it was set in stone. A bit like PEF in general. It is a shame because it ties the implementation to the MetroWerks compiler even more.

But we can work around it so it's not a massive issue.

memsom commented 5 months ago

I'm in a position to pick this back up. Do you have any time to talk through making a BeOS target?

autc04 commented 5 months ago

Yay!

Not much time for the next few days (travelling), but I can occasionally respond.