Cross Development Environment Overview - retrotruestory/M1DEV GitHub Wiki

`Cross Development Environment Overview Software for Magic-1 has primarily been created in a cross-development environment running on an Ubuntu Linux system. The development tools have been built up over time, originally targeting Magic-1 in a bare-metal environment and then later under Magic-1 using the Minix operating system.

The root of the development environment is located at

    workspace/M1Dev

Under that directory are:

    bin/ - where the cross tools live

    cross_tools/ - where tools that can run both on Ubuntu Llinux and Magic-1

    cross_apps/ - code that can be build both for Ubuntu Linux and Magic-1

    lcc/ - source code for the lcc C compiler

    non_minix/ source code for the bootloader and Minix loader

    PromData/ Intel hex files for the microcode

    root/ - source code for Minix and Minix apps

With the proper packages installed, doing a “make clean” and then “make” at the M1Dev level should build all Magic-1 code and tools.

lcc C Compiler

Magic-1’s C compiler is lcc - a retargetable ANSI C compiler written by Fraser and Hanson and is described in the textbook “A Retargetable C Compiler: Design and Implementation.” The compiler can be targeted to new machines by creating a machine description (.md) file. This was done for Magic-1 early in Magic-1’s development. Magic-1’s .md file can be found at M1Dev/lcc/src/m1.md.

There are two versions of lcc in Magic-1’s cross-development: lcc and clcc. “lcc” is used to compile code that does not run under Minix, while “clcc” is used to build code for Minix. The difference between the two compiler drivers is largely where they look for default include files and which versions of C libraries they link against. At this time, “lcc” is used only for building the boot ROM and Minix loader.

Note the clcc and lcc are actually small driver programs that invoke the various tools that make up the compilation process.

To see what clcc does, use the “-v -v” option, for example “clcc -v -v -o helloworld helloworld.c”.

This displays what the compiler will do, but does not actually cary it out. In general, the C preprocessor “cpp” will first be invoked, followed by the heart of the lcc compiler, “rcc”. This will generate an assembly file which may include macros. Next, the Magic-1 assembler, “m1_as” is invoked with an option to run the m4 macro processor using the m4 macros found at M1Dev/root/usr/lib/m1_macros.m4. The resulting object file is then linked using the Magic-1 linker, “m1_ld” and the various C libraries found in M1Dev/root/usr/lib.

There are quite a few command-line options available for lcc. See the man page at M1Dev/lcc/doc/lcc.1. However, among the most useful is “-Wf-g,;”, which intersperses the C source code in the generated assembly file. This enables you to see how the compiler translates the C code into assembly.

The clcc driver program generally uses temp files in /tmp to hold the various intermediate outputs and then deletes them after they are no longer needed. Compiler options can be used to keep them around.

m1_as - the Magic-1 assembler

The source code for the assembler can be found at M1Dev/dual_tools/as. The version built for cross development running under Linux, while the version that can run natively on Magic-1 is “as”. The assembler has gone through a number of revisions during Magic-1 development. Originally it only ran in a cross development environment and used a lot of memory during operation. In order to be able to be run natively on Magic-1, it had to undergo a lot of changes to modify the way it used memory and how it parsed the assembly file.

In short, it was changed to use the file system to build up the output to limit RAM usage.

It is a multi-pass assembler. This was done to generate the most space-efficient code. Branches on Magic-1 take a different amount of space depending on how close the branch target is. During assembly, we don’t know how far away some branch targets are, so worst-case is assumed. After each pass is complete, some of the long branches are found to be able to be changed into short branches. This however, may change subsequent displacements, to things have to be recalculated.

There are clever ways to handle this forward reference process, but Magic-1’s assembler is pretty stupid. After each pass, it recalculates and determines if any changes are necessary. If so, it makes those changes and then does another pass. It continues to make passes across the code until there are no additional changes or a pass limit is reached. I think the most passes ever needed was 8, though most of the time only 2 or 3 are needed.

On a high-powered Linux workstation, the cost of doing extra passes is not noticeable. However, when running Magic-1 natively it was painful. Thus, a change to the assembler was made to cache a tokenized version of the program being assembled in RAM and then iterate over the cached tokens. This sped things up considerably, but for large programs there may not be enough RAM available for the token cache. In that case, there are assembler options to skip the token cache and just iterate over the assembly source code.

To simplify the lcc C compiler retargeting, the Magic-1 machine description pretends that Magic-1’s instruction set architecture includes floating point and 32-bit registers. It then emits macros operating on those “registers”. The assembler therefore includes an option to invoke the m4 macro processor to expand those macros into actual Magic-1 instruction.

To see the various options, do a “m1_as -usage” on Linux or “as -usage” on Magic-1.

m1_ld - the Magic-1 linker

Code linking is a relatively complex operation, and the development of Magic-1’s linker emphasized simplicity and correctness over speed. You will generally not ever invoke the linker directly, but rather use the clcc driver. The clcc driver is smart enough to examine the files presented to it and know whether the c compiler or assembler is needed prior to linking.

In order to generate an executable on Magic-1, the linker will concatenate a special startup file, crt0.o, followed by one or more pre-compiled .o files. It will then search a set of Magic-1 library archives (such as libc.a libm.a, etc.) and finally add the final .o that marks the end of various memory regions: crtn.o.

To see the linker options, do a “m1_ld -usage”. These options can be passed in via the clcc driver using the “-Wl-” mechanism. For example, to generate an executable and also have the linker produce a load map, do: “clcc -Wl-m -o helloworld helloworld.c”. The linker will then produce “helloworld.map” that shows the various post-link addresses.

A useful option is to ask the linker to show the sizes of the various object files that it uses to create an executable. Try:

clcc -Wl-s -o helloworld helloworld.c

Miscellaneous tools

There are quite a few little tools to assist in development:

m1_ar/ar - the Magic-1 archive tool

This is used to create object code libraries. Works as standard Unix “ar” program. Do a “man ar” on Minix for more information.

m1_nm/nm - symbol tool

Displays object file symbols. “m1_nm -usage” for help or “man nm” on Minix.

m1_header - display object file or executable header.

Shows the a.out header.

m1_dis/dis - Magic-1 disassembler

Disassembles an executable. Does not work on .o files, only executables.

m1_lorder/lorder

Tool to assist in the efficient ordering of .o files within a code library. See “man lorder” on Minix.

m1_ranlib/ranlib

Tool to create an index in a code library to speed linking. See “man ranlib”

m1_size/size

Displays the size of the text, data and bss sections of an object file or executable.

m1_strip/strip

Strips the symbols from an executable to save space. `

⚠️ **GitHub.com Fallback** ⚠️