Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Games development on ELKS #871

Open
toncho11 opened this issue Nov 17, 2020 · 146 comments
Open

Games development on ELKS #871

toncho11 opened this issue Nov 17, 2020 · 146 comments
Labels

Comments

@toncho11
Copy link
Contributor

toncho11 commented Nov 17, 2020

Let's take this example of Snake compiled by Turbo C:

https://github.com/ashmew2/snake-game/blob/master/Final.cpp

  • One needs to replace DOS.h calls with ELKS libc or syscalls. Here it looks like only delay, sound and nosound are used. We can omit sound for now.
  • One needs to compile conio.h. An implementation exists based on ncurses, but then one needs to port ncusrses too. But actually only kbhit, clrscr and getch are used in this game! So 3 out 8 functions from conio.h.
  • And finally one needs to rewrite the needed functions from graphics.h. Functions needed for this game are: fillpoly, drawpoly, settextstyle, outtextstyle, outtextxy, setfillstyle, floodfill, setcolor, rectangle

@ghaerr Maybe graphics.h can be mapped to Nano-X? But on 8086 computers Naxo-X looks slow for games. For example

@toncho11 toncho11 added the bug Defect in the product label Nov 17, 2020
@pawosm-arm
Copy link
Contributor

The topic of porting graphic libraries and 90's games to ELKS should be preceded by a more substantial discussion regarding the graphics in ELKS in general. I never had occasion to work with nx before meeting ELKS (my past experience with NuttX from which nx originated never went beyond the nsh command line sessions), so I can't really say how good or bad this library is. I just have impression that I already saw things more mature than nx, like Photon on QNX or DirectFB on Linux. Even things like GUILib for multiplatform SDL library seemed more mature despite all of its limitations. And there is also John Elliott's SdSDL library for SDL which emulates GSX and VDI graphics that originates from CP/M and DOS (before MS Windows). And that was the direction I was looking for: John Elliot's fork of GSX/VDI-based GEM (the mighty FreeGEM) works really lovely on both of my XT machines (one 8088, one 8086 based), far better than nx on ELKS on the same machines, though I suspect the secret is in the underlying OS: there's no multitasking in DOS, hence GEM can consume all of the CPU time for handling mouse-driven graphics operations. Yet anyway, it only shows that doing GUI on XT is possible and the user experience does not need to suffer. BTW, Windows 3.0 was the last version of MS Windows that could be installed on 8086 and it offered some kind of multitasking, still being capable of providing good enough user experience.

One more thing I remember from the past. My very first Linux back in the 90s (some very early version of RedHat) was installed on some very low end i386 machine equipped with ISA 8-bit VGA graphics card (probably the same I've installed during ELKS times into my Turbo XT to replace Hercules mono graphics card that was installed into it originally). Running X at 640x480x16 offered terrible user experience and 16-color palette looked awful. Yet I managed to force X server to run with 320x200x256 mode and it really started to fly! The user experience was amazing. I guess two factors were playing their role here: lower resolution and a whole byte per pixel which simplified all of the graphic operations. I think it's one of the possible directions nx on ELKS could also explore. In the end, those demos don't really display much of the content on screen. See how GEOS looks like on C64, it's low resolution and it's still really cool (speaking of GEOS, it was ported to 8086 XT's too, yet to my disappointment its final XT-compatible version was working much slower than FreeGEM).

@tkchia tkchia added the enhancement New feature label Nov 17, 2020
@tkchia
Copy link
Collaborator

tkchia commented Nov 17, 2020

Hello @toncho11, hello @pawosm-arm,

Well, the graphics stuff aside --- I think @ghaerr can comment further on that --- it is obvious that porting Turbo C++ specific code to, well, anything that is not Turbo C++, is going to take some effort. I do not see this as a bug per se: there is no real urgent need for ELKS to be compatible with a non-standard programming interface (even if it will be a "nice thing to have").

@pawosm-arm
Copy link
Contributor

I remember the times when porting from Turbo C++ was all about getting rid of '#include <conio.h>' :)

@tkchia
Copy link
Collaborator

tkchia commented Nov 17, 2020

Hello @toncho11 , hello @pawosm-arm,

I remember the times when porting from Turbo C++ was all about getting rid of '#include <conio.h>' :)

Well, I guess there are different ways of doing the porting task. :-|

In case you missed it: on the MS-DOS front, I have been working on a libi86 project. It aims to implement, for gcc-ia16, non-standard C library extensions that are present in the Open Watcom C runtime and (to some degree) the Turbo C++ runtime.

I am currently keeping this library separate from the underlying C library (newlib-ia16) that implements the core C99 and POSIX functionality.

(<conio.h> support is mostly complete (for MS-DOS, that is). As for graphics, both Open Watcom and Turbo C++ have very rich graphics libraries, and it is not very easy to "just reimplement" them. And that is even without having to worry about multi-tasking or multi-window interfaces...)

If there is real demand, perhaps I can look into extending the scope of libi86 to also work on ELKS, in addition to MS-DOS.

Thank you!

@pawosm-arm
Copy link
Contributor

I wonder how far we can go with this... It's like with Fuzix for Z80 machines where ability to run CP/M programs was considered.

@Mellvik
Copy link
Contributor

Mellvik commented Nov 17, 2020 via email

@toncho11
Copy link
Contributor Author

Nice digression. We need that 👍

By the way is it possible to change the priority of a process in ELKS? There is a nice sys call, but no nice command? Maybe this will make a game run a bit faster.

While we wait for @ghaerr I think making a layer that allows for quick porting of turbo C programs and games will be interesting. I mean I want to use ELKS and have software for it. Ok some people are focused only on the kernel and they do not care about userland, but not me.

@pawosm-arm
Copy link
Contributor

@toncho11 I think the 'bug' label here is slightly misleading.

@Mellvik,

Interesting digression @pawosm-arm, I'm curious: anything in particular from cp/m you're interested in?

Although interesting, this digression should not grow here as it barely touches the matter of this ticket.
As for CP/M, having beefed up Spectrum +3 in a workable condition I've got a native machine to run CP/M Plus with all of the stuff I remember from the 80s. I also have Spectrum Next that also can boot CP/M Plus yet comparing to +3, the support for on-board peripherals isn't complete which leaves me with a sense of disappointment.

It may be worthwhile remembering that cp/m and fuzix ran on a different and incompatible architecture (from x86), and most programs were written in assembler. Still, I have 3 operating cp/m machines...

I'm slightly confused here. Both Fuzix and CP/M were originally targeting Z80 machines hence the idea of running CP/M software in Fuzix wasn't that terrible (considering simplicity of CP/M). And this simplicity was a key here: I suspect providing similar compatibility layer in ELKS to enable running 8088/8086 DOS programs would be more difficult.

In case of CP/M, attempts to support other architectures (e.g. CP/M-86 or CP/M-68k) never gained popularity due to competition from the natively developed operating systems that were better suited to those machines.

Anyway, there is some trace of the feature I've mentioned here in Fuzix's git repo: https://github.com/EtchedPixels/FUZIX/blob/master/Applications/cpm/runcpm.c

EOOT.

@ghaerr
Copy link
Owner

ghaerr commented Nov 18, 2020

Hello everyone,

Well, there's a lot that got discussed today, here's a few of my thoughts...

one needs to rewrite the needed functions from graphics.h. Functions needed for this game are: fillpoly, drawpoly, settextstyle, outtextstyle, outtextxy, setfillstyle, floodfill, setcolor, rectangle

Yes, defining a distinct set of graphics functions, preferably across more than just one game, is a great way to start. Then, the entire function-set can be analyzed a bit and a graphics engine/library used as an API-bridge. Having done a heck of a lot of graphics engine programming, I also highly recommend keeping things simple in the beginning, and adding complexity only after getting the initial game(s) initially drawing. For instance, fill, draw poly and rectangle are pretty basic, but floodfill can get complicated. Text output is a whole 'nother discussion, but if a fixed-size font can be used, things are simplified considerably.

Maybe graphics.h can be mapped to Nano-X? But on 8086 computers Naxo-X looks slow for games.

Yes, a lot of the graphics could be mapped to Nano-X, but it will likely be too slow. The biggest reasons for this (to be covered in more detail later) are 1) the lower level routines are written in C, not ASM, and 2) Nano-X has an additional layer of overlapping window management and clipping support.

I have a number of graphics translation libraries for various embedded systems like Arduino, but what is really needed here is an (older) 8086 graphics library with direct, fast EGA support.

I never had occasion to work with nx before meeting ELKS (my past experience with NuttX from which nx originated never went beyond the nsh command line sessions), so I can't really say how good or bad this library is. I just have impression that I already saw things more mature than nx, like Photon on QNX or DirectFB on Linux. Even things like GUILib for multiplatform SDL library seemed more mature.

Actually, Nano-X originated from NanoGUI, which itself was based on some very early code by David I. Bell, the same guy that interestingly enough wrote sash and a lot of the basic command line utilities used in ELKS. I took that code back in the 1990's and started the Microwindows Project, whose original goal was to implement the Win32 GDI as well as an Xlib-like API onto Linux framebuffer systems (those with video cards implementing a flat frame buffer rather than the complicated and slow original EGA/VGA card hardware). Back in 1999, version 0.86 was the last version small enough to run on 8086 systems, and I ported it to ELKS. So we're running a code snapshot from 21 years ago. The landmine game is particularly old-fashioned looking; it was written by David Bell himself and a contributed port to Microwindows was made over 20 years ago. There are lots of other much better-looking Nano-X graphics, but most all are too big to run on ELKS,. The name was changed from Microwindows to Nano-X when Microsoft sent a cease and desist letter. Check out what Nano-X looks like today; unfortunately its all way too big for ELKS.

But I digress. Back to @pawosm-arm's points, SDL, DirectFB, etc - are all too large for ELKS. Since the ELKS applications (not the kernel) will be writing directly to the EGA, what we should seriously consider would be an older, fast EGA library. Those libraries would likely use a different assembler than ia16-gcc-as (which is gcc compatible and AT&T syntax). Another option would be to use the EGA draw routines from Nano-X written in ASM (elkcmd/nano-X/drivers/asmplan4.s), but they would need to be ported from the BCC/AS86 assembler, and the speed would likely improve considerably. Nano-X is written as three layers (driver, engine and API), and the driver and engine could be used without the window-management API overhead, which would help. Its all a bit complicated, but doable.

By the way is it possible to change the priority of a process in ELKS? There is a nice sys call, but no nice command? Maybe this will make a game run a bit faster.

There's no way to change the priority of a process in ELKS (yet); but when a single program is running, which is usually the case, there's no need for it - the system runs the single process as fast as can be, only taking timer interrupts, which increment a few counters, and then the interrupted process is immediately continued.

@toncho11
Copy link
Contributor Author

toncho11 commented Nov 18, 2020

There is another solution:

Alfonso Martone found a way to convert DOS Turbo-C programs to enable these to run with ELKS. For this he developed the exe2elks DOS utility program and a small libc without DOS system calls. You compile your C program with this small libc library instead of the standard Turbo-C libc. Then you use the exe2elks utility to convert the generated executable in the DOS EXE format to a 16bit OMAGIC aout file as required by ELKS.

So if we accept that the Turbo C graphics.h is not using DOS system calls (I actually do not know) then we could compile a game with Turbo C and:

  • either use implementations of all the I/O libraries that do not use DOS calls as Alfonso does
  • the game comes as an object file from Turbo C that does not contain the IO libraries and we continue with ia16 gcc compiler on Linux which links the implementations

So again:

  • custom script that compiles the game core and links the graphics.h to an object file using Turbo C
  • link the rest from ia16 gcc using @tkchia versions of conio.h and dos.h.

printf should come from ia16 gcc I suppose.

What do you think? Are the differences in compiling linking and function parameters handling so different ?
This solution can solve the problem of speed because I believe Turbo C routines are already optimized.

@pawosm-arm
Copy link
Contributor

Actually, Nano-X originated from NanoGUI, which itself was based on some very early code by David I.

Argh, this name reuse, so common in Open Source world. Indeed nx graphics in NuttX and nx graphics in ELKS are completely different things of completely different origins. Both had the same purpose in common which is being lightweight, and that confused me. So porting nx from NuttX may be one more path to follow or avoid.

Anyway, ability to change resolution (and bytes per pixel) would be a great addition to current graphics layer in ELKS, but only if it doesn't overcomplicate the current codebase.

@pawosm-arm
Copy link
Contributor

Speaking of Turbo C++, it brings me some of my late school days memories, you can have a glimpse here, https://www.youtube.com/watch?v=N8z5kQFhJ9A yet note I'll probably pull it down soon, there's too much cringe in this :)

Anyway, looking forward, if you're serious about DOS compatibility in ELKS, IPX would be a nice addition, we had a lot of fun playing those early multiplayer games back in the 90's.

@ghaerr
Copy link
Owner

ghaerr commented Nov 18, 2020

@toncho11,

Alfonso Martone found a way to convert DOS Turbo-C programs to enable these to run with ELKS. For this he developed the exe2elks DOS utility program and a small libc without DOS system calls. You compile your C program with this small libc library instead of the standard Turbo-C libc.

I have to say, somewhat amazing piece of work by Alfonso. He's definitely provided the basics, that should, possibly without modifications, work today (we upgraded the ELKS a.out format slightly earlier this year, but still support the original a.out format, calling it "v0").

The bigger issue with this approach is that the development is all done on DOS, right? So TurboC is run on DOS to compile and link any TC program, along with the TC graphics library, and Alfonso's DOS-to-ELKS conversion libc which allows the program to run on ELKS without modification. It's a slick way to solve this problem. Perhaps you should try running his sample programs and see if/how they run on ELKS today, for a start.

@ghaerr ghaerr added discussion and removed bug Defect in the product labels Nov 18, 2020
@toncho11
Copy link
Contributor Author

toncho11 commented Nov 18, 2020

I compiled a minimal program that draws a rectangle with Turbo C and then applied exe2elks.
For the map file generated by Turbo C and required by exe2elks there are 3 options "Segments", "Publics" and "Detailed". I think I wen with "Segments".

It says "invalid argument" on ELKS.

@ghaerr
Copy link
Owner

ghaerr commented Nov 18, 2020

I compiled a minimal program that draws a rectangle with Turbo C and then applied exe2elks.
It says "invalid argument" on ELKS.

This is very likely due to the exe2elks output a.out binary format not being compatible with ELKS now. We will need to make some changes to exe2elks.c. I won't be able to compile it, since I don't have access to DOS. Take a quick look at exe2elks.c, and familiarize yourself with it. Make sure you can compile it up from scratch to exe2elks.exe. Please post the non-working minimal program binary, and then I'll help determine what fields in the "elks" struct we need to change. I don't think it will be much.

@toncho11
Copy link
Contributor Author

Here it is:
DEMO3.zip

@toncho11
Copy link
Contributor Author

If this is the correct disassembly:
DEMO3.txt

then it means that there are 19 int $0x21 DOS calls ... more than I have hoped.

@toncho11
Copy link
Contributor Author

toncho11 commented Nov 18, 2020

OK I see now ... probably I need to use the .bat file provided by Alfonso to set the proper options of tcc and tlink ... to generate a better executable ... I was using the IDE with .map file generation enabled.

@ghaerr
Copy link
Owner

ghaerr commented Nov 18, 2020

probably I need to use the .bat file provided by Alfonso to set the proper options of tcc and tlink ... to generate a better executable

Yes, the idea is to compile with TC but link with Alfonso's special library that replaces all the TC libc calls to int 21h with ELKS system calls, and then rewrite the .EXE MZ format to ELKS a.out format.

@toncho11
Copy link
Contributor Author

toncho11 commented Nov 18, 2020

Ok it is going to take much more time.
Turbo C loads a graphical driver. So that is why there were many int 21 calls I suppose. It access the disk and it loads the driver - a EGAVGA.BGI file in my case as I am using VGA card. The good news is that I can convert the .bgi to .obj with bgiobj.exe and use it while linking .

@toncho11
Copy link
Contributor Author

toncho11 commented Nov 19, 2020

This is a direct example of Alfonso:

HELLO.zip

It says "invalid argument". We need to fix that first before going deeper into the graphics library.

How do you disassemble it?
objdump -s -x -D -b binary -m i8086 hello
It does not print all the lines I think ?

@ghaerr
Copy link
Owner

ghaerr commented Nov 19, 2020

@toncho11,

I'm not exactly sure what the problem is with ELKS running that (v0 format a.out header) executable, but I have a workaround that works:

chmem -h can be run on the executable, setting the same heap size as the program says it wanted originally; chmem will then set the header to type v1 and it will run. This should work on any executables produced by Alfonso's tools for the time being. I'll look into and fix either ELKS or your elks2exec.c so this won't be required after v0.4.0.

Screen Shot 2020-11-19 at 4 28 28 PM

How do you disassemble it?

It appears we don't have a way to disassemble linked binaries... the objdump86 program should be able to work, but it seems to want to work on .o files only. I'll look into that later also, unless you can find a decent 8086 disassembler on the net somewhere. I would like to add disassembly capability directly into an ELKS command.

@toncho11
Copy link
Contributor Author

It is far more complicated ... I can not even compile a normal exe with the integrated bgi driver.

@pawosm-arm Do you know how to do that? How to compile from command line with graphics.lib, egavga.obj and registerbgidriver(EGAVGA_driver) ? I can not make it to work.

@toncho11
Copy link
Contributor Author

Interesting ... I tried to contact Alfonso ... but there is no working email ... nor any other contact information ...

@pawosm-arm
Copy link
Contributor

pawosm-arm commented Nov 20, 2020

@pawosm-arm Do you know how to do that? How to compile from command line with graphics.lib, egavga.obj and registerbgidriver(EGAVGA_driver) ? I can not make it to work.

Can you specify more details? Which 'command line': DOS, Linux, ELKS? (well, ELKS command line doesn't seem to have a linker). What kind of binary do you want to have in effect?

@ghaerr
Copy link
Owner

ghaerr commented Nov 20, 2020

I can not even compile a normal exe with the integrated bgi driver.

Compile or link error? You're using TC on DOS so there should be no issues having TC compiling TC programs, including those with graphics.h. Alfonso's trickery is all at the link stage, linking with additional libraries, right? You'll probably need to study TC a bit and understand what options it has for graphics programs, should there be compilation issues with it.

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

it is reset because reenigne uses every register for computation.

In that case, SP could be reset to a hard number like 0x1c00 as long as you have either made space for it in the .data section by looking at the map file (as ORG statements won't work correctly) or are sure that that address is within the heap area below the 42K data area being used. Still, DS should not be reset to the .text/CS segment which allows DS == SS for the duration of the execution.

The following code in elks/arch/i86/kernel/irqtab.S is used to check that SS == DS during hardware interrupts or syscalls. This is only checked current when CONFIG_TRACE (default OFF) is on:

utask:
        mov     current,%si
#ifdef CHECK_SS
//
//      We were in user mode, first confirm
//
        mov     %ss,%di
        cmp     TASK_USER_SS(%si),%di // entry SS = current->t_regs.ss?
        je      utask1          // User using the right stack
//
//      System got crazy
//
        mov     $pmsg,%ax
        push    %ax
        call    panic
utask1:
#endif

Here the idea is to prepare data segment (or section?) in such a way that at DS:0 we have a large temporary uninitialized array used later for calculations.

Yes that should work - the kernel only cares that the .data (and stack) segment are that which is set on program entry in DS and that the program doesn't use memory outside its allocated segment, since there is no HW memory management.

@Vutshi
Copy link

Vutshi commented May 22, 2024

Does ELKS prepare all three registers like this SS = DS = ES = data segment?
At least this is what I see happening inside martypc debugger.

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

Almost. SS = DS = data segment. The C startup and/or kernel may be setting ES as well, but that isn't actually necessary, ES is a free segment register and can be changed/used for what you'd like. It is required to be used as a destination register for various 8086 string instructions, for example. The C compiler requires that ES be saved between function calls, but in ASM you can do what you want.

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

You can run chmem on your program to see what the default values are that the kernel uses to calculate the sizes of the .text and .data (+heap+stack) segments. Zero values displayed for heap or stack will default to 4K each.

@Vutshi
Copy link

Vutshi commented May 22, 2024

In that case, SP could be reset to a hard number like 0x1c00 as long as you have either made space for it in the .data section

this is very illuminating. Thank you. There was a magic constant 0x1c40 used to initialize stack and which I didn’t understand. Now it becomes clear why a tiny stack of size 0x80 is defined with such a large shift.

@Vutshi
Copy link

Vutshi commented May 22, 2024

Still, DS should not be reset to the .text/CS segment

Is there a method to copy a piece of the .text/CS segment (array colourTableInit) to the .data/DS segment without temporary resetting DS to CS?

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

Is there a method to copy a piece of the .text/CS segment (array colourTableInit) to the .data/DS segment without temporary resetting DS to CS?

Not easily, but now that I understand what is happening, its OK to temporarily reset DS. This is done by certain far C library function where the source and destination are in different segments. DS is also reloaded by the C compiler when a __far pointer is used. And the kernel only checks that SS is proper, not DS so you should be OK, as long as the program is keeping track of what the original DS is/was (usually by pushing it on the stack, then popping afterwards).

@Vutshi
Copy link

Vutshi commented May 22, 2024

You can run chmem on your program to see what the default values are that the kernel uses to calculate the sizes of the .text and .data (+heap+stack) segments.

I did this and realized that default 4K+4K is too small. So now I set it manually:
./ia16-elf-gcc -melks-libc -mcmodel=small -nostdlib mand_elf_v2.o -o mand_el2 -maout-heap=0xEFFF -maout-stack=0x0100

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

I did this and realized that default 4K+4K is too small. So now I set it manually:

Unfortunately, that won't work well. -maout-heap=0xFFFF is a special marker which says allocate all you've got, versus a fixed amount, which, like 0xEFFF won't run the program if not available. The stack doesn't need to be specified separately as it is part of the max data area you're asking for with 0xFFFF. Plus you reset SP anyways. I advise using -maout-heap=0xFFFF and leave out any stack setting.

Now that I think of it, there are some other kernel checks (e.g. when calling brk or sbrk) and/or when kernel stack checking is turned on: these will complain when SP is lower than the max SP size allocated at the top of the data segment. So when you set SP very low, this happens. Lets not worry about that for now, as I don't think that is happening, although you won't be able to see it since the console is in graphics mode! :)

@Vutshi
Copy link

Vutshi commented May 22, 2024

MartyPC is written in Rust and that's supposed to be extremely memory safe. And how a process can bring down macOS is a bit confounding.

I am an (un)lucky owner of the last Intel based MacBook Pro. On the one hand, it is very interesting from the computer archeology point of view. On the other hand, Apple seems to totally lost interest in fixing bugs for my hardware. It just crashes once in a while.

@Vutshi
Copy link

Vutshi commented May 22, 2024

I found one I used for a bit in the old days when ELKS was being converted from bcc to gcc. The entire set of ELKS .s files had to be converted. Let me we if I can find that one.

That would be awesome.

@Vutshi
Copy link

Vutshi commented May 22, 2024

Unfortunately, that won't work well. -maout-heap=0xFFFF is a special marker which says allocate all you've got, versus a fixed amount, which, like 0xEFFF won't run the program if not available. The stack doesn't need to be specified separately as it is part of the max data area you're asking for with 0xFFFF. Plus you reset SP anyways. I advise using -maout-heap=0xFFFF and leave out any stack setting.

I hope that after conversion to GAS assembler the compiler will be able to figure out the size required on its own. Everything is precisely and explicitly defined in the data section after all.

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

Everything is precisely and explicitly defined in the data section after all.

If the 42K data area and stack are already defined statically in the .data segment, then no extra heap is required. The stack could be set small as you had since apparently SP is reset anyways. We'll still have the same issues with certain kernel checks and still ignore them for now.

I hope that after conversion to GAS assembler the compiler will be able to figure out the size required on its own

GAS conversion won't do anything different, a perfect conversion would output an almost identical .o file, with the exception of section names might be different. And even if a C compiler were used, our ia16-elf-gcc doesn't do any stack requirement calculations. Currently, the only way to actually learn stack and heap requirements are either manually through source code inspection or running the new CONFIG_TRACE with strace set, which reports stack usage on each system call. Since your program doesn't really make any system calls after init, that probably doesn't do much good either.

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

I found one I used for a bit in the old days when ELKS was being converted from bcc to gcc. The entire set of ELKS .s files had to be converted. Let me we if I can find that one.

That would be awesome.

I don't know where the script I used went, but have you looked into these two: they seem they could work well or have a larger following:

Ubuntu Intel2Gas
https://manpages.ubuntu.com/manpages/jammy/man1/intel2gas.1.html
https://launchpad.net/ubuntu/+source/intel2gas/1.3.3-19

Ta2as:
https://github.com/mefistotelis/ta2as

Let me know how/if these work for you.

@Vutshi
Copy link

Vutshi commented May 22, 2024

Btw, this is how ELKS + misbehaving Mandelbrot break MartyPC dbalsom/martypc#116 (comment)

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

@Vutshi - I tried the ta2as conversion program above, and once I got it to compile on macOS (strlwr is missing) it seemed to convert your mandelbrot source semi-kind-of-well, for the instructions it will convert. The major problem(s) you might run into is that NASM is using macros and those aren't converted or aren't available.

FYI here's the patch I used to get ta2as to compile in case you're interested:

diff --git a/make.sh b/make.sh
old mode 100644
new mode 100755
diff --git a/src/main.c b/src/main.c
index 7410ca2..30b91d8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -83,3 +83,17 @@ int main(int argc, char *argv[])
        fclose(out);
        return 0;
 }
+
+#include <ctype.h>
+
+char *strlwr(char *str)
+{
+  unsigned char *p = (unsigned char *)str;
+
+  while (*p) {
+     *p = tolower((unsigned char)*p);
+      p++;
+  }
+
+  return str;
+}
diff --git a/src/ta2as.c b/src/ta2as.c
index 9f78fd5..6dfe20f 100644
--- a/src/ta2as.c
+++ b/src/ta2as.c
@@ -18,6 +18,8 @@
 #include <string.h>
 #include <ctype.h>
 
+extern char *strlwr(char *str);
+
 typedef void (*modfunc)(AsmLine *ln);
 
 typedef struct {

@Vutshi
Copy link

Vutshi commented May 22, 2024

Thank you @ghaerr.

First I try to employ the newly available GPT-4o. When it doesn't work I will switch to more conventional tools :)

@Vutshi
Copy link

Vutshi commented May 22, 2024

ChatGPT doesn't want to just do a plain conversion it tries to optimize :)

shr di,1
shr di,1

converted to:

shrw $2, %di

@Vutshi
Copy link

Vutshi commented May 22, 2024

Well, it kind of works...

ELKS_GAS_Mandel.mov

here is the GAS code
mandel_elks.s.zip

@ghaerr
Copy link
Owner

ghaerr commented May 22, 2024

What did you use for conversion, ta2as? I'm guessing GPT didn't do it for you...

Something to consider during Intel to AT&T conversion: input NASM syntax like

   mov ax,foo

could mean

  mov ax,[foo] ; normal access of memory contents

or instead the less likely

mov ax, foo ; foo is a constant and not a memory address

I think NASM does not require the [foo] syntax for the memory move is foo is a label, instead of an EQU.

The translator would not know the difference between them, while an assembler would. This might be an issue to check into.

@Vutshi
Copy link

Vutshi commented May 23, 2024

What did you use for conversion, ta2as? I'm guessing GPT didn't do it for you...

Actually, I mainly used ChatGPT because it understands macros. Although it has a mind of its own and tends to reorganize functions ordering according to its liking. I cleaned up the code to match the objdump of the NASM-generated object file. Now, I have a one-to-one matching of NASM and GAS codes except for the .data section.

Essentially elf2elks forces me to introduce non empty .data otherwise it complains:

elf2elks: error: data and BSS sections overlap!

so instead of this in NASM:

section .data
absolute 0
iters:
  resb itersX*itersY
  alignb 2
aTable:
  resw itersX
bTable:
  resw itersY
yTableLower:
  resw 102 ;itersY
yTableUpper:
  resw 102 ;itersY
itersXTable:
  resw itersY
colourTable:
  resb 35
squareTableSegment:
  resw 1
video_mode:
  resb 1
stackStart:
  resb 128

I do this in GAS:

.data
.byte 0x01, 0x02

.bss
.local iters, aTable, bTable, yTableLower, yTableUpper, itersXTable, colourTable, squareTableSegment, video_mode, stackStart
.comm iters, itersX*itersY, 2
.comm aTable, itersX * 2, 2
.comm bTable, itersY * 2, 2
.comm yTableLower, 102 * 2, 2
.comm yTableUpper, 102 * 2, 2
.comm itersXTable, itersY * 2, 2
.comm colourTable, 35, 1
.comm squareTableSegment, 2, 1
.comm video_mode, 1, 1
.comm stackStart, 128, 1

and compile as follows:

./ia16-elf-gcc -melks-libc -mcmodel=small -nostdlib mandel_elks.s -o mand_els -maout-heap=0xffff

It works but the .data section reserves 16 bytes and everything is shifted because program assumes that .bss is at DS:0.
How to get rid of the .data section completely?

@ghaerr
Copy link
Owner

ghaerr commented May 23, 2024

@Vutshi: You are indeed making great progress traveling fairly deep into an interesting linking rabbit hole :)

How to get rid of the .data section completely?

I'm thinking of two possibilities to consider: 1) remove the check for .data and .bss section overlap in elf2elks, and allow the link to proceed with a null .data segment, which should cause no problem, or 2) modify the default elks/elks-small.ld linker script to remove the requirement for the .data section.

The first option will be easiest at first, and if it works I could come up with allowing an overlap iff the size of .data == 0. It appears that elf2elks has never worked with an ELF conversion (from C) that doesn't have null .data segment. This is likely because the startup C crt0.S is always included and sets aside DS:0 to protect NULL data pointer dereferences:

    .data

// Zero data for null pointers (near & far)
// Will be linked as first section in data segment

    .section .nildata

    .word 0
    .word 0

(Section .nildata is linked first in the linker script into the data segment).

To try the elf2elks mod, edit elks/tools/elf2elks/elf2elks.c as follows:

  check_scn_overlap (text_sh, "text", ftext_sh, "far text");
  check_scn_overlap (text_sh, "text", data_sh, "data");
  check_scn_overlap (text_sh, "text", bss_sh, "BSS");
  check_scn_overlap (ftext_sh, "far text", data_sh, "data");
  check_scn_overlap (ftext_sh, "far text", bss_sh, "BSS");
  check_scn_overlap (data_sh, "data", bss_sh, "BSS"); // <--- comment out this line

Run make in the ELKS root and a new elf2elks will be created.

The possibly better way might be to modify the elks-small.ld linker script, but they are a bit of black magic. Here's the portion that may need to be renamed and/or deleted:

    .data 0x30000 : { // <--- rename this section to .olddata
        /* IA-16 segment start markers. */
        *(".nildata!*" ".nildata.*!")
        *(".rodata!*" ".rodata.*!")
...
    .bss : { // <--- change this to .bss 0x30000
        *(.bss .bss$* ".bss.*[^&]")
        *(COMMON)
...
// in the asserts that follow, change .data to .bss
        ASSERT (. + 0x100 - ADDR (.data) <= 0xfff0
            "Error: too large for a small-model ELKS a.out file.");
        /* Sanity check any -maout-total= and -maout-chmem= values */
        PROVIDE (_total = 0);
        PROVIDE (_chmem = 0);
        ASSERT (_total <= 0xfff0
            && . - ADDR (.data) + _chmem <= 0xfff0,
            "Error: total data segment size too large.");
        ASSERT ((_total == 0 || _total > . - ADDR (.data))
            && _chmem >= 0,
            "Error: total data segment size too small.");
    }

The changed linker script can then be passed to the linker as ia16-elf-gcc -T <linker_script> ... in your link command.

I mainly used ChatGPT because it understands macros. Although it has a mind of its own and tends to reorganize functions ordering according to its liking

Wow, impressive!!

Now, I have a one-to-one matching of NASM and GAS codes except for the .data section.
It works but the .data section reserves 16 bytes and everything is shifted because program assumes that .bss is at DS:0.

I see, very nice. It sounds like you're getting quite close to having this fully worked out!

@Vutshi
Copy link

Vutshi commented May 23, 2024

It's done.

ELKS_GAS_Mandel_final.mov

The bug with stripes was due to SP reset to 0x1c00. One should not touch SP in a multitasking OS indeed. I just use cmpw $0x1c00, %ax which costs 19 bytes more than cmpw %sp, %ax.

@FrenkelS thanks for the trick, now I use it as well ;)

There is a couple of weird quirks left.

  1. I tried to jump to exit if far memory is not allocated successfully:
  38:	cd 80                	int    $0x80
  3a:	85 c0                	test   %ax,%ax
  3c:	0f 85 fd 00          	jne    13d <exit>

But the CPU (or MartyPC) sees it differently:
weird_jump

  1. GAS doesn't like this code:
.equ MULTIPLIER, 0x600
.equ maxX, 320
.equ maxY, 101
.equ initialShift, 5
.equ initialGrid, (1 << initialShift)
.equ	itersX,	((maxX + initialGrid - 1)/initialGrid)*initialGrid + 1
.equ	itersY,	((maxY + initialGrid - 1)/initialGrid)*initialGrid + 1

it complains about the last two lines as follows Error: found ' ', expected: ')

Best
code&binary_v2.zip

@Vutshi
Copy link

Vutshi commented May 23, 2024

@ghaerr, I forgot to say that I used the first option to fix .data section problem

\\check_scn_overlap (data_sh, "data", bss_sh, "BSS"); // <--- comment out this line

@ghaerr
Copy link
Owner

ghaerr commented May 23, 2024

This is great, well done!!

The bug with stripes was due to SP reset to 0x1c00. One should not touch SP in a multitasking OS indeed. I just use cmpw $0x1c00, %ax

I see, was this previously using SP to determine the calculation depth of the fractal?

I haven't studied your source code yet, does this final version leave SS & SP alone, or it SP still being reset to within the lower part of the data segment?

@FrenkelS thanks for the trick, now I use it as well ;)

What trick is that, I didn't see it.

0f 85 fd 00 jne 13d
But the CPU (or MartyPC) sees it differently:

In the 8086, 0x0F is an invalid opcode that in early versions of the 8086 was a "POP CS". Since that instruction, changing the code segment, but not the IP at the same time, is useless, the same instruction prefix was used in later CPUs as the beginning of a new multi-byte opcode. To fix this, either use jne short label or add ..arch i8086, nojumps to your .S file. The opcode that is being generated in your case is a "long conditional" jump, which won't actually run on real 8086 hardware.

I used the first option to fix .data section problem

I'll make a note to enhance elf2elks to allow . bss without data when .data size is 0.

it complains about the last two lines as follows Error: found ' ', expected: ')

I'm not sure what that is, perhaps rearrange the lines or add a few blank lines or spaces in each line to see whether that changes the error message.

@Vutshi
Copy link

Vutshi commented May 23, 2024

I see, was this previously using SP to determine the calculation depth of the fractal?

Yes. The reason is probably to just save one byte. At least, I don't see any speed decrease.

does this final version leave SS & SP alone

Yes.

@FrenkelS thanks for the trick, now I use it as well ;)

What trick is that, I didn't see it.

The black and white Mandelbrot turns green in the end.

Thank you.

@ghaerr
Copy link
Owner

ghaerr commented May 23, 2024

.equ MULTIPLIER, 0x600
.equ maxX, 320
.equ maxY, 101
.equ initialShift, 5
.equ initialGrid, (1 << initialShift)
.equ	itersX,	((maxX + initialGrid - 1)/initialGrid)*initialGrid + 1
.equ	itersY,	((maxY + initialGrid - 1)/initialGrid)*initialGrid + 1
#.equ tempX, (maxX + initialGrid - 1)//initialGrid
#.equ itersX, (tempX*initialGrid + 1)
#.equ tempY, (maxY + initialGrid - 1)//initialGrid
#.equ itersY, (tempY*initialGrid + 1)
#.equ itersX, 321
#.equ itersY, 129

Were you saying that the top 7 lines work, and the lower 7 do not, with GAS? Certainly GAS will not like the C++ // comments apparently used for division for NASM?

@Vutshi
Copy link

Vutshi commented May 23, 2024

Were you saying that the top 7 lines work, and the lower 7 do not, with GAS? Certainly GAS will not like the C++ // comments apparently used for division for NASM?

No, here lines 6 and 7 are the problem for GAS. NASM likes them. Commented lines are my attempts to fix it with GAS.

@Vutshi
Copy link

Vutshi commented May 25, 2024

Hi @ghaerr,
I broke something:

./ia16-elf-gcc -melks-libc -mcmodel=small -nostdlib mandel_elks.s -o mand_els88
elf2elks: error: ia16-elf-gcc: internal compiler error: Segmentation fault: 11 (program elf2elks)
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.

It happens after addition of the following line:

.data
.comm evil, 0x8000, 1

mandel_elks 2.s.zip

Best

@ghaerr
Copy link
Owner

ghaerr commented May 25, 2024

I broke something

I would guess that by adding 32K bytes to .bss with the .comm directive you've overflowed .bss and / or the data section but the modified elf2elks isn't bounds/range checking anymore and an internal copy crashed it. A finally version of elf2elks will need to do more than just turn off overlap checking when sizeof .data == 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants