Serentty / rusty-dos

A Rust skeleton for an MS-DOS program for IBM compatibles and the PC-98, including some PC-98-specific functionality
144 stars 9 forks source link

djgpp ? #3

Open stuaxo opened 5 years ago

stuaxo commented 5 years ago

djgpp has been packaged for Linux https://launchpad.net/~stsp-0/+archive/ubuntu/djgpp/+packages could that help, or is the aim just to have 16 bit code ?

I'm pretty fuzzy on how this works (though managed to compile the example and run in dosbox).

I read your post - https://www.reddit.com/r/rust/comments/ask2v5/dos_the_final_frontier/

As I'm new to rust, I'm pretty fuzzy on how this works, and where to extend things to play with this..

BlogOS has some VGA text mode routines, which looks like an interesting place to start playing with, though at the moment, not even sure how to build another file apart from dos.com

jwt27 commented 4 years ago

I think the main problem here is that rustc generates PE-COFF objects. I'm quite surprised this even links at all, and that both djgpp's and mingw's objdump recognize the files as their own. I knew the formats were similar but I didn't think they would be totally ambiguous. The relocations look valid (the same) in both objdumps however. I guess the line "llvm-target": "i386-pc-windows-gnu" in the target json file sets the object format , but it seems i386-pc-msdosdjgpp is not recognized. Or at least, I get an 'object format not recognized' error when building.

However, I'm pretty much entirely sure that puts() gets the wrong address entirely because it ends up printing entirely garbage instead of the correct string followed by garbage.

This is because PE-COFF puts const strings in an .rdata section, but this section is not linked in by the djgpp linker script. Try searching the exe for the "Hello, DJGPP" string... it's not there :)

Serentty commented 4 years ago

Try searching the exe for the "Hello, DJGPP" string... it's not there :)

That is indeed very strange considering that it has no problem actually showing a string when I write it to the screen using my own routine. Maybe it is including it somewhere else since it's typed as a byte array instead of a string. I did that because it's not in UTF-8, which Rust strings must be.

image

jwt27 commented 4 years ago

Try searching the exe for the "Hello, DJGPP" string... it's not there :)

That is indeed very strange considering that it has no problem actually showing a string when I write it to the screen using my own routine. Maybe it is including it somewhere else since it's typed as a byte array instead of a string. I did that because it's not in UTF-8, which Rust strings must be.

It's because that loop is unrolled and each byte is written individually:

    movl    $72, %ecx
    movl    $15, %edx
    calll   __ZN5dos322io11write_entry17h0442a9727dcda470E
    movl    $101, %ecx
    movl    $15, %edx
    calll   __ZN5dos322io11write_entry17h0442a9727dcda470E
    movl    $108, %ecx
    movl    $15, %edx
    calll   __ZN5dos322io11write_entry17h0442a9727dcda470E
    movl    $108, %ecx
    movl    $15, %edx
    calll   __ZN5dos322io11write_entry17h0442a9727dcda470E
    ...
Serentty commented 4 years ago

That makes sense. So you checked and confirmed that the compiler is generating PE object files instead of COFF ones? Is there any easy solution for this?

jwt27 commented 4 years ago

The only real solution I think, is to add support for the coff-go32 target in LLVM.

A workaround might be to have a binutils compiled with --enable-targets=all, then you can use objcopy to convert the PE objects to coff-go32 format, and use a custom linker script that links the special PE sections in the sections that djgpp uses.

Or an even dirtier hack; make rustc emit asm, then fixup the section names with a script and assemble it with i386-pc-msdosdjgpp-as...

Serentty commented 4 years ago

I wonder if the version of Binutils that I have from the DJGPP build that I got might have PE support. If not, it seems I'll probably end up with yet another dependency.

stuaxo commented 4 years ago

Can you use objcopy to convert it into the correct format ?

From some googling, it looks like it can convert from COFF to PE.

Every time I read these, I end up going on a google about executable or object formats, but never seem to find something quite useful enough :)

jwt27 commented 4 years ago

Can you use objcopy to convert it into the correct format ?

I tried this, and the relocations are still wrong. So the object format may not be the problem after all, and maybe PE can be similar enough to go32 that it's safe to link with each other, at least for simple programs.

stuaxo commented 4 years ago

Apologies for the noise, I should have scrolled up :)

Is there much involved to add support, I guess it's pretty parts already there?

arbruijn commented 4 years ago

It seems djgpp uses the value of a relative relocation differently, it adds the offset of the relocation in the section. I wrote a little perl script to change the relocation format of a windows coff to the djgpp variant and rename the .rdata section to .text. It did work for a tiny puts(b"hello\0") rust program.

coff-to-djgpp.pl

Serentty commented 4 years ago

@arbruijn This is huge! I'll try it out very soon.

Enet4 commented 4 years ago

@arbruijn Thank you for sharing this. I'm afraid that I haven't gotten it to work yet. Is it only meant to work on non-linked COFF object files? How did you apply the script in the building process?


It's also worth noting that there were a few recent breaking changes to the target specification schema. A specific change last May has changed post-link-objects to become a JSON object instead of an array. Let us not forget to write down the exact nightly toolchain that compiles the program, preferably in the rust-toolchain file so that Rustup picks it up automatically.

arbruijn commented 4 years ago

There's probably a better way, but what I did was: (using rustc 1.46.0-nightly (ff5b446d2 2020-06-23))

  1. Create new rust project with cargo new rustdos

  2. Add the libc crate to Cargo.toml under [dependencies]: libc = "0.2.0"

  3. Edit src/main.rs:

    
    #![feature(start)]
    #![feature(lang_items)]
    #![no_main]
    #![no_std]
    use core::panic::PanicInfo;

extern crate libc;

fn hello() { unsafe { libc::puts(b"hello\0".as_ptr() as *const i8); } }

[start]

[no_mangle]

pub extern "C" fn main() -> i32 { hello(); 0 }

[panic_handler]

fn handle_panic(_info: &PanicInfo) -> ! { loop {} }

[lang = "eh_personality"]

extern "C" fn eh_personality() {}



4. Figure out rustc libc path argument: `cargo build --verbose` (fails with a linker error)

5. Run `rustc` with emit=obj and the extern libc path found from previous command: `rustc --extern libc=C:\src\rustdos\target\debug\deps\liblibc-98230d028222fce0.rlib --emit=obj src/main.rs`

6. Now there's a `main.o`. Run it through the converter: `perl coff-to-djgpp.pl main.o > maindj.o`

7. Then link the `maindj.o` with the djgpp C compiler: `gcc -o maindj.exe maindj.o`

8. This produces a `maindj.exe` that runs in dos.
stuaxo commented 3 years ago

I don't know if it will help, but there is now a GCC frontend for Rust https://rust-gcc.github.io/

fschulze commented 3 years ago

@stuaxo Interesting, that would also open up other architectures like the CPU of the Dreamcast, which isn't yet supported by LLVM

abbec commented 3 years ago

Just as a quick FYI, I did a little experiment with protected mode DOS and Rust over here (with OpenWatcom so not really related to this issue): https://github.com/abbec/dos-rs. It is for sure a horrible hack, probably full of mistakes, but it does work :)

Serentty commented 3 years ago

@abbec That's really nice! Protected mode is a logical choice given that we're forced to target an 80386 regardless. I think your approach is a better one than I started out with here.

volkertb commented 1 year ago

There is also another project for programming real mode applications in Rust, called rust_dos. Not sure if it supports a way to enforce 16-bit (8086) instructions only.

Serentty commented 1 year ago

Nice to see someone else working on this! I will check it out.

cknave commented 1 year ago

Converting the object files looked interesting, though I had problems getting it to work with more complicated code. I ended up going further down that path to convert i586-unknown-none-gnu ELF objects to DJGPP COFF objects.

It worked better than I was expecting. I'm able to use DJGPP's memalign and free as a Rust global allocator, and BTreeMap and Vec seem to work.

Unpolished elf2djgpp converter and example project: https://github.com/cknave/elf2djgpp

Serentty commented 1 year ago

@cknave This is great! Is i586 required, or is that just the target you chose? I have been going for i386, since that is the oldest supported by LLVM.

cknave commented 1 year ago

I don't see any reason i386 would be a problem, my copy of DJGPP was i586 so that's what I used.

Serentty commented 1 year ago

Great! This sounds like an exciting development!

leap0x7b commented 1 year ago

Found this project that tries to do that but differently https://github.com/jayschwa/dos.zig

Rather than linking to DJGPP directly, it uses a linker script that converts the ELF executable into a valid DJGPP COFF executable when converted into raw binary. It's Zig but it should work with Rust.

Enet4 commented 1 year ago

Converting the object files looked interesting, though I had problems getting it to work with more complicated code. I ended up going further down that path to convert i586-unknown-none-gnu ELF objects to DJGPP COFF objects.

It worked better than I was expecting. I'm able to use DJGPP's memalign and free as a Rust global allocator, and BTreeMap and Vec seem to work.

Unpolished elf2djgpp converter and example project: https://github.com/cknave/elf2djgpp

Just here to mention that I tried this out on my end and it worked! It only needed a few very minor changes to run on Python 3.10 and to link with i686-pc-msdosdjgpp-gcc. I could also integrate this with the code in my test project, and make DOSBox run a "Hello world" program without apparent issues. It's true that the translation process is slow right now, maybe the tool itself could be rewritten in Rust? :upside_down_face:

Enet4 commented 1 year ago

So I was back on this subject some months later to find out that @cknave rewrote elf2djgpp in Rust, making it much faster! :tada: That made me dig deeper into how a Rust program would use the APIs available when programming for DJGPP-GCC, including going to video mode and back to text mode. The proof of concept is here. :crab:

Screenshot_2023-10-15_19-26-53

It would be interesting to try making a write-up of what has been proven possible now, how much was done to get to the current point, and where to go from here. I could give it a try, although it can take time. Without some prior research I feel that there would be many key details which could go unmentioned or be described inaccurately.

volkertb commented 1 year ago

@Enet4 Cool development! Thanks for sharing. :slightly_smiling_face: The link to your proof of concept results in a 404 though, at least for me.

Enet4 commented 1 year ago

The link to your proof of concept results in a 404 though, at least for me.

Ah, oops! I thought I had made the repository public already! :sweat_smile: There you go. :)

https://github.com/Enet4/dos-rs

Enet4 commented 1 year ago

I was also on the look out for other projects just now, and found another approach I believe was not yet mentioned here. In rust-le-demo, an ELF executable file is built, and then converted to a DOS LE executable with a new tool elf2le. The proof of concept shows a working allocator. If it can interact with the system peripherals as easily as in DJGPP, then it may well be a key solution to writing Rust programs for protected mode DOS.

ceionia commented 1 year ago

Hey! Been paying attention to this thread for a few years, I'm the author of that rust-le-demo project, although I'm a bit ashamed of the quick & dirty code, the approach does work. Definitely not as easy as DJGPP, just because there's no C library available. I've considered rewriting parts of DOS C libraries for DOS/32 or converting Watcom C libraries to ELF to try to reduce the amount of user implementations and DOS extender calls needed, but linking properly with DJGPP would probably be nicer. If there's gonna be movement in any direction for Rust on DOS, I'd definitely be interested in helping!

Enet4 commented 1 year ago

It's great to have you here @LCeionia!

Since my last update, I was able to:

The way I see it, this can go in a few directions:

volkertb commented 1 year ago

@LCeionia Would the picolibc project perhaps be of use here? It's a lightweight and portable C library, primarily targeting embedded systems with limited hardware resources. But those characteristics might make it useful for this as well, right?

By the way, thank you all for your efforts in making Rust a viable programming language for DOS development. There is just something really cool about combining old tech with new tech. :slightly_smiling_face: