Open stuaxo opened 5 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 :)
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.
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
...
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?
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
...
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.
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 :)
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.
Apologies for the noise, I should have scrolled up :)
Is there much involved to add support, I guess it's pretty parts already there?
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.
@arbruijn This is huge! I'll try it out very soon.
@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.
There's probably a better way, but what I did was:
(using rustc 1.46.0-nightly (ff5b446d2 2020-06-23)
)
Create new rust project with cargo new rustdos
Add the libc crate to Cargo.toml
under [dependencies]
:
libc = "0.2.0"
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); } }
pub extern "C" fn main() -> i32 { hello(); 0 }
fn handle_panic(_info: &PanicInfo) -> ! { loop {} }
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.
I don't know if it will help, but there is now a GCC frontend for Rust https://rust-gcc.github.io/
@stuaxo Interesting, that would also open up other architectures like the CPU of the Dreamcast, which isn't yet supported by LLVM
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 :)
@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.
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.
Nice to see someone else working on this! I will check it out.
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
@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.
I don't see any reason i386 would be a problem, my copy of DJGPP was i586 so that's what I used.
Great! This sounds like an exciting development!
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.
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
andfree
as a Rust global allocator, andBTreeMap
andVec
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:
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:
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.
@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.
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. :)
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.
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!
It's great to have you here @LCeionia!
Since my last update, I was able to:
Vec
and company.i586-msdosdjgpp-gcc
and elf2djgpp
. A cold build takes around 1m30s, with caching it should be even faster.The way I see it, this can go in a few directions:
libc
crate because they are only exposed in platforms the crate knows about. Since i386-unknown-none-gnu
is not seen as a UNIX platform, we don't get to find useful functions in it (printf
, puts
, malloc
, free
, etc). Not that I blame the crate in particular, it makes sense not to expose something that likely won't link in the end. This only means that the various low-level extern "C"
APIs, including libc, need to be declared manually, which is what I ended up doing to get the parts needed. This process was manual because bindgen
would choke on the header files, but if anyone finds a better way, that would be nice.dos_x
module in the example project contains what would eventually become a more idiomatic API for interfacing with the system. It is very bare and sparse right now (it holds just enough constructs for setting the video mode, writing to the video buffer, little more), but it would be interesting to see this flourish into a complete and usable framework, perhaps even with an API inspired by dos-like (and dos-like-rs).@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:
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