-2

I'm using an i686-elf-gcc cross compiler to generate code to run in real-mode.

I'm trying to read a sector from my kernel. I know this is the location where my kernel is located, in the second sector, 0th drive, 0th track. It builds fine, but after I call read, sectors_read is still 0.

u8 status;
u8 sectors_read;
read(1, 0, 1, 0, 0, status, sectors_read);
kprint("STATUS: ");
hex_to_ascii(status, num_str_buffer);
kprint(num_str_buffer);
kprint("\nSECTORS READ: ");
num_str_buffer[0] = '\0';
hex_to_ascii(sectors_read, num_str_buffer);
kprint(num_str_buffer);
void read(u8 sector_size, u8 track, u8 sector, u8 head, u8 drive, u8 status, u8 sectors_read)
{
    asm volatile("mov $2, %AH");
    asm volatile("mov %0, %%AL" : : "r"(sector_size) : );
    asm volatile("mov %0, %%CH" : : "r"(track) : );
    asm volatile("mov %0, %%CL" : : "r"(sector) : );
    asm volatile("mov %0, %%DH" : : "r"(head));
    asm volatile("mov %0, %%DL" : : "r"(drive));
    asm volatile("int $0x13");
    asm volatile("mov %%AH, %0":"=r"(status) : );
    asm volatile("mov %%AL, %0":"=r"(sectors_read) : );
}
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
mike
  • 100
  • 7
  • It might be easier to just write the entire disk reading routine in assembly. E: this is totally personal preference, but I find inline assembly to be very error-prone – adrian Jun 21 '20 at 20:01
  • 5
    You need all of that to be part of *one* asm statement; there's zero guarantee the compiler won't pick AL for the `"r"(track)` operand, for example, overwriting the AL you wrote earlier. You also didn't tell the compiler about register you modify (missing a clobber) so it's totally unsafe. This is the opposite of how to use GNU C inline asm, read a tutorial (https://stackoverflow.com/tags/inline-assembly/info) and/or look at the compiler-generated asm you get from compiling this. – Peter Cordes Jun 21 '20 at 20:01
  • 1
    Also note that as always, arguments in C are passed by value. You seem to be expecting the value of `sectors_read` in your calling function will be changed by the call to `read`, but since it's pass by value, it cannot change. So whatever value you're seeing in it after the function is just the same uninitialized garbage value that was in it beforehand. Your compiler ought to be giving you all kinds of warnings about uninitialized variables (if you turn on warnings and optimization) - pay attention to it! – Nate Eldredge Jun 21 '20 at 20:08
  • Are you sure you are still in real mode when this code runs? If you like, I can give an example of how to fix your inline assembly so it would work if everything else was correct. – fuz Jun 21 '20 at 20:10
  • 3
    By the way, you don't seem to provide a buffer to read data into anywhere. Where do you expect the data to be read to? – fuz Jun 21 '20 at 20:13
  • Are you compiling with GCC's `-m16` option and running this in real mode? (not protected mode or 64-bit long mode) – Michael Petch Jun 21 '20 at 20:15
  • @MichaelPetch I suppose OP might be using `ia16-gcc`, but I can't tell without confirmation. – fuz Jun 21 '20 at 20:16
  • @fuz : They could be yes, but many don't know about that fork and it is still somewhat experimental. But I agree we need to find out what GCC compiler they are actually using. – Michael Petch Jun 21 '20 at 20:17
  • I (and probably others) have downvoted you because you have not responded to any comments. Asking a question is a two-way street: without engaging the people who try to help you, you are just wasting everybodys time. The downvotes indicate to other people that they should not waste any extra time on a question where the author doesn't respond. – fuz Jun 21 '20 at 21:05
  • 1
    I have a small project that is a 2 stage bootloader that runs in real mode (needs a 386+ to run). https://github.com/mpetch/OSDev/blob/master/examples/gcc-2stage-bootloader/ . biosdsk.h has an example of inline code that may be useful. I implement a couple of function like disk read (with retries) and disk reset . I pass disk data around through a structure pointer to save on having to pass around many parameters to the function. I also have an SO answer that has info in the `notes` section that applies to this question: https://stackoverflow.com/a/52047408/3857942 – Michael Petch Jun 21 '20 at 21:40

1 Answers1

2

There's a bunch of stuff wrong with your read function. As you have not posted a reproducible example or even enough details to know what toolchain you are programming for, I cannot give a definite answer to your problems. Here are a number of things that are wrong:

  • in C, all function arguments are passed by value. Assigning to status and sectors_read in read will not affect the arguments you pass in the callee. Pass a pointer instead.
  • you do not tell the compiler that you clobber ax, cx, and dx or that the contents of these registers are of importance. The compiler might thus use them for whatever purpose it likes, interfering with the semantics you intend. To fix this, use register constraints instead of mov instructions to place the arguments into the right registers.
  • You do not seem to set es:bx to the address of a buffer to read data into. Thus, the BIOS will read data into whatever address es:bx points to, essentially overwriting random memory.

Here is an example how you could fix these issues in the read function. Note that there are likely more changes needed depending on your situation.

typedef unsigned char u8;
typedef unsigned short u16;

void read(u8 sector_size, u8 track, u8 sector, u8 head, u8 drive,
    u8 *buffer, u8 *status, u8 *sectors_read)
{
    u16 result;

    asm volatile("push %%cs; pop %%es; int $0x13"
        : "=a"(result)
        : "a"(0x200|sector_size), "b"(buffer),
          "c"(track<<8|sector), "d"(head<<8|drive)
        : "es", "memory");

    *status = result >> 8;
    *sectors_read = result >> 0;
}

The code above assumes ia16-gcc. For i686-gcc, use

    asm volatile("int $0x13"
        : "=a"(result)
        : "a"(0x200|sector_size), "b"(buffer),
          "c"(track<<8|sector), "d"(head<<8|drive)
        : "memory");

instead.

Please understand that without your cooperation and your responses to the comments people wrote under your question, it is not possible to give any better help than this.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • is this using ia16-gcc? I ask because I don't think normal GCC allows ES as a clobber. Regular GCC requires CS=DS=ES=SS and it won't save/restore ES. I haven't pulled up a copy of ia16-gcc to test out how it handles things. – Michael Petch Jun 21 '20 at 20:30
  • Of course this assumes that the disk doesn't have more than 256 cylinders and isn't using a 10-bit cylinder. I also assume you mean number of sectors rather than sector size (I guess that's really an issue with the OP's code). The code also wouldn't allow a buffer to be set outside the code segment. You could pass a new value for ES into the inline assembly save it with push (and restore it after) and set the value in ES with the a value passed through an input operand using a register constraint. – Michael Petch Jun 21 '20 at 20:47
  • 2
    @MichaelPetch This code is meant as an example to OP. It very likely needs to be corrected for whatever purpose OP has in mind. But I can't say what exactly is needed if OP refuses to respond. – fuz Jun 21 '20 at 20:50
  • I finally installed an ia16 elf cross compiler. I hadn't played around with it full before especially with inline assembly. I see it does support `es` as a register and will preserve it as a clobber. I'll assume you are using it. – Michael Petch Jun 21 '20 at 21:55
  • i686-elf-gcc is the version i'm using. Sorry, I was away. Had those typedefs to in another file. – mike Jun 22 '20 at 01:16
  • @mike Next time, please stay with your question for at least an hour after asking it. Anyway; if you want further help, please provide further details. [edit] your question to add these. – fuz Jun 22 '20 at 14:02
  • 1
    You're missing a `"memory"` clobber; gcc needs to know that the pointed-to memory has been written by this asm statement. In my version on [error: unsupported size for integer register](https://stackoverflow.com/q/62541926), I fixed that and removed the ES stuff, because regular GCC requires ES=DS=SS and doesn't allow an ES clobber. – Peter Cordes Jun 24 '20 at 00:22
  • 1
    @PeterCordes Thanks. I addressed this issue in an edit. – fuz Jun 25 '20 at 16:19
  • I wonder if it's possible to use `register unsigned char track asm("ch")` or something to get the compiler to do byte `mov` instructions instead of *actually* shifting an ORing. There isn't a CH constraint, just `"Q"` for a reg that has a high-8 part (EAX,EBX,ECX,EDX). Or maybe a better approach for efficiency is Michael Petch's [Assembly: Unable to read sectors after the first track](https://stackoverflow.com/a/52047408) which passes args in a data structure, hopefully allowing the compiler to do word loads. I don't care enough to actually spend more time on this, though. – Peter Cordes Jun 25 '20 at 19:36
  • 1
    @PeterCordes I did try that before but gcc didn't seem to honor it. Clang certainly did. Same with `Q` constraints. – fuz Jun 25 '20 at 23:44
  • I think it reads and writes before calling int 13. At first it just returned a bunch of 0x02 errors in AH, but then I realized that status will always be what opcode you write in AH. – mike Jun 27 '20 at 03:39