Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
blog:ql_dev1 [2021/12/14 09:55] – john | blog:ql_dev1 [2021/12/16 11:03] (current) – john | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Sinclair QL Development Tools ====== | ====== Sinclair QL Development Tools ====== | ||
+ | There are a range of development tools and languages for the QL, including the built-in SuperBASIC. But I am more interested in using the same type of tools that I use for the NEC PC-98, MS-DOS, X68000 and more; a reasonably standards compliant C compiler, assembler and linker. | ||
+ | |||
+ | There are a few C compiler options for the Sinclair QL - which are summarised nicely here: http:// | ||
+ | |||
+ | I touch on a few of them, including the one I am using (C68, in it's modern cross-compiler incarnation) below. | ||
==== C68 Self-hosted on the QL ==== | ==== C68 Self-hosted on the QL ==== | ||
+ | * http:// | ||
+ | |||
+ | Purported to be the main C compiler for Sinclair QL systems. This is the original version intended to run on the QL itself. | ||
==== C68 Cross-Compiler (aka XTC68) | ==== C68 Cross-Compiler (aka XTC68) | ||
- | https:// | + | |
+ | |||
+ | A port of C68 to DOS, Windows and Linux systems was made in the late 90's to earl 2000' | ||
+ | |||
+ | As suggested, this runs as a cross-compiler; | ||
+ | |||
+ | I've posted a pre-compiled version of the toolchain, with the original C68 runtime libraries (crt.o, libc.a et-al) and system headers, below. | ||
* {{ : | * {{ : | ||
+ | * {{ : | ||
+ | * {{ : | ||
+ | === Sample C68 Makefile === | ||
+ | |||
+ | You can of course use standard GNU Makefiles alongside C68, here's a basic one that compiles a single binary from two .C source files, as you would normally do with anything of reasonable complexity. | ||
+ | |||
+ | Note that I specifically **do not** have my C68 toolchain directories in my path, as I have far too many compilers installed to manage them if they were all fighting for priority on the path. I tend to set things up so that I either temporarily add them to the front of the path, as per the makefile below, or have a shell script temporarily set the path for a given session. | ||
+ | |||
+ | It's often easier to keep paths and things like that encapsulated in the Makefile, so you can see that at the top of the example below: | ||
+ | |||
+ | < | ||
+ | # Sample makefile for C68 projects | ||
+ | |||
+ | ################################# | ||
+ | # Temporarily add the cross | ||
+ | # compiler location to the path | ||
+ | ################################# | ||
+ | SHELL := /bin/bash | ||
+ | PREFIX = / | ||
+ | PATH := ${PREFIX}/ | ||
+ | |||
+ | ################################# | ||
+ | # Locations and name of compilers | ||
+ | # and friends | ||
+ | ################################# | ||
+ | CC = qcc | ||
+ | LD = qld | ||
+ | AS = as68 | ||
+ | CPP = qcpp | ||
+ | |||
+ | ################################# | ||
+ | # Paths to additional headers and | ||
+ | # library files | ||
+ | ################################# | ||
+ | #INCLUDES = -I./ | ||
+ | INCLUDES = | ||
+ | #LIBS = -L./my_libs -lmylib | ||
+ | LIBS = | ||
+ | |||
+ | ################################# | ||
+ | # Compiler flags | ||
+ | ################################# | ||
+ | ASFLAGS = | ||
+ | LDFLAGS = -v | ||
+ | CFLAGS = -O | ||
+ | |||
+ | ################################# | ||
+ | # What our application is named | ||
+ | ################################# | ||
+ | TARGET = game | ||
+ | |||
+ | ################################# | ||
+ | # Targets to build/run | ||
+ | ################################# | ||
+ | all: $(TARGET) | ||
+ | #dev: | ||
+ | #test: | ||
+ | |||
+ | ################################# | ||
+ | # Object files needed to build our | ||
+ | # main application | ||
+ | ################################# | ||
+ | OBJFILES = test.o other.o | ||
+ | |||
+ | ################################# | ||
+ | # Main application target build recipe | ||
+ | ################################# | ||
+ | $(TARGET): | ||
+ | $(LD) $(LDFLAGS) $(OBJFILES) $(LIBS) -o $(TARGET) | ||
+ | |||
+ | ############################### | ||
+ | # Clean up | ||
+ | ############################### | ||
+ | clean: | ||
+ | rm -f *.o | ||
+ | rm -f $(TARGET) | ||
+ | |||
+ | ################################ | ||
+ | # Each C source is compiled to | ||
+ | # an object file, to be linked | ||
+ | # as a single binary, above. | ||
+ | ################################ | ||
+ | test.o: test.c | ||
+ | $(CC) $(CFLAGS) $(INCLUDES) -c test.c | ||
+ | |||
+ | other.o: other.c | ||
+ | $(CC) $(CFLAGS) $(INCLUDES) -c other.c | ||
+ | </ | ||
+ | |||
+ | === Running C68 === | ||
+ | |||
+ | If you use a sample makefile such as the one above, you should get an output such as this: | ||
+ | |||
+ | < | ||
+ | $ make | ||
+ | qcc -O -c test.c | ||
+ | qcc -O -c other.c | ||
+ | qld -v test.o other.o | ||
+ | ld v1.22 QL 68000 SROFF Linker | ||
+ | COMMENT: C68 crt_o v4.24f | ||
+ | COMMENT: C68 libc_a v4.24f | ||
+ | |||
+ | --------------------------- | ||
+ | SECTION | ||
+ | --------------------------- | ||
+ | TEXT | ||
+ | DATA 3ad6 2FA | ||
+ | UDATA | ||
+ | --------------------------- | ||
+ | Program length | ||
+ | Relocation table = 1ef | ||
+ | -------------------- | ||
+ | Memory Usage | ||
+ | Buffer Usage | ||
+ | -------------------- | ||
+ | game: dataspace 870 (366) | ||
+ | |||
+ | Link completed | ||
+ | </ | ||
+ | |||
+ | This is mostly easy to understand - two passes of the compiler; one for each source file, then the linker takes the two object files, adds-in //crt.o// and any functions from the //standard library//. The output is a little more verbose than the usual compiler though - an important element being the // | ||
+ | |||
+ | If you hexdump the binary, you will see some trailing data that mirrors that // | ||
+ | |||
+ | < | ||
+ | $ hexdump -C game | ||
+ | . | ||
+ | . | ||
+ | . | ||
+ | 00003fa0 | ||
+ | 00003fb0 | ||
+ | 00003fc0 | ||
+ | 00003fc8 | ||
+ | </ | ||
+ | |||
+ | This is referred to as the //xtcc field// and is used by the **qlzip** utility when unpacking on the QL native filesystem to restore the needed metadata about the application. | ||
==== GCC Cross-Compiler ==== | ==== GCC Cross-Compiler ==== | ||
+ | |||
+ | * https:// | ||
+ | |||
+ | This has the potential to be a great resource for people such as myself who are primarily unix developers. Sadly, this was a project started a long time ago, and as such, is based on a horribly dated version of GCC 2.95.3 - almost 20 years old at the time of writing (2021). | ||
+ | |||
+ | As such, it is a real pain to get working on any reasonably modern version of Linux. The project above has attempted to make it easier by building all of the tools inside an earlier Debian-based docker container, but I simply could not get it to work reliably; the install procedure is a bit of a mess of installing components to bootstrap the build of other components, then overwriting various binaries with the stages of later output. It's all a bit of a mess (and no fault of the guy trying to make it easier!). | ||
+ | |||
+ | It should, in theory, build outside of a docker container, and I got it //mostly// working, but when invoking the built version of gcc, the linker stage of a test C program would blow up with a signal 6 (ABORT). | ||
+ | |||
+ | It's a shame, as other platforms (Atari ST, for example) that are very similar to the QL, have much more modern versions of the GCC | ||
+ | |||
+ | ==== Working with QL files & devices ==== | ||
+ | |||
+ | The QL has it's own filesystem and files have attributes which are not directly equivalent to anything on a modern filesystem - storing the size of the amount of memory needed to load the binary, for example. So there are some tools needed to get anything you are working on, say in a Linux filesystem, into a QL-native format. | ||
+ | |||
+ | * **qlzip** | ||
+ | * https:// | ||
+ | * Any binaries produced by C68 can be zipped up, have their extended data stored, and then unpacked with the correct data in place using a native QL unzip tool. | ||
+ | |||
+ | * **qltools** | ||
+ | * https:// | ||
+ | * Works with native QL floppy disks (DD 720K, HD 1440K or ED 3.2MB) or disk images to transfer files to/from those devices from Linux. | ||
+ | |||
+ | ==== Other Stuff ==== | ||
+ | |||
+ | I wrote a quick Python script to print out the value of an XTcc field in a binary produced by C68, GCC or similar. This is useful when copying an executable into a QL floppy or hard drive filesystem and you need to set the //dataspace parameter//: | ||
+ | |||
+ | < | ||
+ | # | ||
+ | |||
+ | #################################### | ||
+ | # | ||
+ | # Prints out the value of a Sinclar | ||
+ | # QL binary XTcc field, as needed to | ||
+ | # turn it into an executable file | ||
+ | # on the QL itself, or when transferred | ||
+ | # using qltool. | ||
+ | # | ||
+ | # John Snowdon, 2021 | ||
+ | # | ||
+ | #################################### | ||
+ | |||
+ | import sys | ||
+ | import os | ||
+ | |||
+ | if len(sys.argv) < 2: | ||
+ | print(" | ||
+ | sys.exit(-1) | ||
+ | |||
+ | ql_filename = sys.argv[1] | ||
+ | if (os.path.exists(ql_filename)): | ||
+ | f = open(ql_filename, | ||
+ | filedata = f.read() | ||
+ | offset = filedata.find(b' | ||
+ | |||
+ | if offset: | ||
+ | # First byte of " | ||
+ | f.seek(offset + 5) | ||
+ | dataspace = f.read(4) | ||
+ | |||
+ | # Print out the value of the dataspace field | ||
+ | print(int.from_bytes(dataspace, | ||
+ | |||
+ | f.close() | ||
+ | |||
+ | else: | ||
+ | print(" | ||
+ | sys.exit(-1) | ||
+ | </ | ||
+ | |||
+ | Just call the script with the name of your C68 executable. For example: | ||
+ | |||
+ | < | ||
+ | $ xtcc game.bin | ||
+ | 868 | ||
+ | </ | ||
+ | |||
+ | ... the value printed is the size of the dataspace needed. So you would then copy and mark your binary with qltools as follows: | ||
+ | |||
+ | < | ||
+ | $ xtcc game.bin | ||
+ | 868 | ||
+ | $ qltools floppy.img -W game.bin | ||
+ | $ qltools floppy.img -x game.bin 868 | ||
+ | </ | ||
+ | |||
+ | Or even easier, embed the xtcc call in backticks: | ||
+ | |||
+ | < | ||
+ | $ qltools floppy.img -W game.bin | ||
+ | $ qltools floppy.img -x game.bin `xtcc game.bin` | ||
+ | </ | ||
+ |