phil-opp / blog_os

Writing an OS in Rust
http://os.phil-opp.com
Apache License 2.0
15.58k stars 1.07k forks source link

Comments for "https://os.phil-opp.com/cpu-exceptions/" #450

Closed utterances-bot closed 3 years ago

utterances-bot commented 6 years ago

This is a general purpose comment thread for the “CPU Exceptions” post.

dodikk commented 6 years ago

Got a compiler error while trying out the code from listing.

error: use of deprecated item 'x86_64::structures::idt::Idt': type was renamed to `InterruptDescriptorTable`, the old name will removed in the next major version
  --> src/main.rs:56:31
   |
56 | use x86_64::structures::idt::{Idt, ExceptionStackFrame};
   |                               ^^^

The error is obvious to fix. Still, it would be a better experience to have correct listings out of box.

P.S. My code has the x86_64 = "0.2.6" dependency as recommended in the previous articles.

A dedicated github issue : https://github.com/phil-opp/blog_os/issues/452

phil-opp commented 6 years ago

Yeah, we released a new x86_64 version with this renaming a few days ago. I thought it's better to wait a bit before updating the post to avoid breakage for the users on older versions. Normally the deprecation only causes a warning instead of an error, so I thought this wouldn't break new users.

The question is why it's an error for you. Are you explicitly using deny(warnings) or something like that?

dodikk commented 6 years ago

The question is why it's an error for you. Are you explicitly using deny(warnings) or something like that?

Yes, that's the case. I've continued the discussion (a few more questions from me) in a dedicated ticket.

yjhmelody commented 6 years ago

I consider it strange to write these exception codes to main.rs or bin/*.rs. Why not write them to lib.rs which can pub a code.

phil-opp commented 6 years ago

Because we want to do different things on an exception (compare the implementations of the breakpoint_handler functions).

Edit: We moved the exception handlers to an interrupts module in https://github.com/phil-opp/blog_os/pull/475.

k0pernicus commented 5 years ago

Thank you first for this awesome blog post! There is an error in the part "Loading the IDT", where, just before the subpart "Lazy Statics to the Rescue", you are declaring your IDT variable as an Option<...>. I think you have to remove the Option type here :)

Good luck, and thanks again!

phil-opp commented 5 years ago

@k0pernicus You're right, thank you! Fixed in 442da8c9eb4f6ce86aa3969415cf9e8591cf3b1f.

lilyball commented 5 years ago

Why does src/interrupts.rs have extern crate x86_64;? That's already done in src/lib.rs.

phil-opp commented 5 years ago

Good catch! Removed in d9f3b3d.

JoshMcguigan commented 5 years ago

Hello, and thanks for your work on this blog series! I've been learning a lot by working through it.

One thing I was unsure about in this section, is why does the integration test not rely on our new interrupts module? It seems the test is actually checking whether the x86_64 crate is correct. Am I missing something here?

phil-opp commented 5 years ago

@JoshMcguigan Good point! I think I already had the test for double faults in mind when writing this post. The double fault test makes sense because it checks that we have a correct GDT and TSS setup, but the test in this post requires no previous setup so it really tests only the x86_64 crate.

I don't think that we can reuse the interrupts module for the test, because we need to do different things in the handler functions. Therefore I propose to simply remove it. I opened https://github.com/phil-opp/blog_os/pull/526 for this.

AntoineSebert commented 5 years ago

Is there a way to define your own interrupts with dedicated handlers (without colliding with the hardware interrupts to implement later) ?

phil-opp commented 5 years ago

@AntoineSebert You mean software interrupts? Yes, you can just use any of the higher IDT entries (i.e. the entries not used for exceptions or hardware interrupt).

AntoineSebert commented 5 years ago

@phil-opp Since the OS is a x86-64 architecture and you use usize to access the elements, does it means that the theoretical maximal number of interrupts (and the IDT's max size) is 18.446.744.073.709.551.615 ? So I can just assign a number higher than 47 (the last hardware interrupt's number) to an new interrupt ?

phil-opp commented 5 years ago

@AntoineSebert No, the Idt size is limited to 256 entries because the int instruction uses an u8 for the interrupt number. Accessing Idt[256] or higher will result in a panic, similar to index operations on arrays.

So I can just assign a number higher than 47 (the last hardware interrupt's number) to an new interrupt ?

Yes. For example, you could set Idt[60] to a handler function and then invoke int 60 to run it.

AntoineSebert commented 5 years ago

@phil-opp But instead of defining all the interrupts one by one as independent variables, wouldn't be more convenient to declare them inside a structure (like a map or an enum) ?

phil-opp commented 5 years ago

@AntoineSebert I'm not quite sure what you mean. You mean the individual table entries? These are predefined by the hardware, so we have to follow this specification.

AntoineSebert commented 5 years ago

@phil-opp For now we only have

pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET;
pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1;

as hardware excpetions, covered in a next post). If we choose to support the other hardware exceptions, we ill end up with something like this

pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET;
pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1;
pub const OTHER_INTERRUPT_ID: u8 = PIC_1_OFFSET + 2;
pub const SERIAL_PORT_2_INTERRUPT_ID: u8 = PIC_1_OFFSET + 3;
pub const SERIAL_PORT_1_INTERRUPT_ID: u8 = PIC_1_OFFSET + 4;
pub const PARALLEL_PORT_2_3_INTERRUPT_ID: u8 = PIC_1_OFFSET + 5;
pub const FLOPPY_DISK_INTERRUPT_ID: u8 = PIC_1_OFFSET + 6;
pub const PARALLEL_PORT_1_INTERRUPT_ID: u8 = PIC_1_OFFSET + 7;
pub const REAL_TIME_CLOCK_INTERRUPT_ID: u8 = PIC_2_OFFSET;
pub const ACPI_INTERRUPT_ID: u8 = PIC_2_OFFSET + 1;
// and so on...

Do you think it is possible to put all the interrupts' names and numbers in a (no-std) map ?

phil-opp commented 5 years ago

@AntoineSebert Thanks for clarifying! I agree that the constants are not really convenient. I think a repr(u8) enum would work better. It would also have the advantage of causing an error if we accidentally use the same index for different interrupts.

I would be happy to merge a pull request (against the post-08 branch) that implements this!

AntoineSebert commented 5 years ago

@phil-opp glad you like the idea ! :D But I'm a complete Rust beginner, I only have a vague idea of how to do it. I'm gonna try, although I cannot promise the result will be good, but at least it will be rustic. Here is my code so far:

#[derive(Debug)]
#[repr(u8)]
pub enum HardwareInterrupt {
    Timer = PIC_1_OFFSET,
    Keyboard,
    Other,
    SerialPort2,
    SerialPort1,
    ParallelPort2_3,
    FloppyDisk,
    ParallelPort1,
    RealTimeClock,
    Acpi,
    Available1,
    Available2,
    Mouse,
    CoProcessor,
    PrimaryAta,
    SecondaryAta,
}

Only the first member is specified, the following are inferred (demo). Compile & run, no linter warnings. However every time I use one of the members, I have to cast it with as u8, and that makes me sad :(

phil-opp commented 5 years ago

Looks good, I like the inferred numbers! For the blog, we only need the Timer and Keyboard variants for now. And maybe we could name the struct InterruptId or InterruptIndex or something similar. Regarding the cast problem: There was a recent Pre-RFC proposal to avoid the as u8 cast. Alternatively, adding an as_u8 method that encapsulates the cast is possible too. Edit: For the blog the as u8 cast is fine.

To create a pull request, simply modify the interrupts.rs file in the post-08 branch. You can either fork the repo and do it locally, or just use GitHubs web editor.

Edit: This was implemented in #557.

AtsukiTak commented 5 years ago

Hi. Thank you for awesome blog series!

I just want to clarify a point.

The x86-interrupt calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return.

At above sentence, does terms all register mean all global register? Since I thought that, for example,rax register is included in all register but it seems not. I'm not sure global register is right terms but you would grab what it means :)

phil-opp commented 5 years ago

@AtsukiTak I'm glad you like the series!

The x86-interrupt calling convention does restore all general purpose CPU registers, including rax, rbx, rcx, and so on. Additionally, the CPU (not the compiler) push the segment registers (cs, ss), the stack and instruction pointers, and the RFLAGS register. This is independent from the used calling convention since this interrupt stack frame is pushed directly by the CPU.

I hope this clarifies things!

AtsukiTak commented 5 years ago

@phil-opp Now things are very clear. Thanks a lot!!

sunyulin728 commented 5 years ago

Hi, Thank you! This is awesome blog.

I tried to run the rust code in KVM. All other part works fine except the exception part.

No matter how I load the IDT, either in guest mode or host mode. The exception handler can't be triggered.

Do you by chance know ther reason?

64 commented 5 years ago

@sunyulin728 can you link to your code? I'd be happy to take a look.

See also https://wiki.osdev.org/Troubleshooting

sunyulin728 commented 5 years ago

@64 , Thank you.

The code is at https://github.com/sunyulin728/rustkvm. Sorry that it is just experiement code and the related path is hardcoded.

1, It need to be clone to /home/brad/rust/rustkvm

  1. cd /home/brad/rust/rustkvm/
  2. ./setup
  3. cd qkernel
  4. make
  5. cd ../qvsior
  6. Cargo run
64 commented 5 years ago

@sunyulin728 I haven't run the code myself (it's a little tricky to setup) but it sounds like it's an issue with either the GDT or paging - but it's pretty difficult for me to diagnose since you're going through a hypervisor.

If you're running in QEMU you can use -d int to print detailed information about the crash that you're getting which can help you diagnose the issue.

I understand that this isn't the best place for extended help so if you have further questions I'd be happy to answer them on the rust-osdev gitter.

sunyulin728 commented 5 years ago

@Matt Taylor, Thank you!

I agree with you. The issue is very likely from GDT setup.

I translate the KVM sample from https://github.com/david942j/kvm-kernel-example to Rust. But the orginal code didn't setup GDT at all.

I study the gdt setup code in https://os.phil-opp.com/double-fault-exceptions/. I find there is only cs and tss setup. There is no other *s such ds setup related code.

Do you know why?

phil-opp commented 5 years ago

@sunyulin728

I study the gdt setup code in https://os.phil-opp.com/double-fault-exceptions/. I find there is only cs and tss setup. There is no other *s such ds setup related code.

According to the AMD/Intel manuals, the content of the other segment registers are ignored in 64-bit. See https://github.com/rust-osdev/x86_64/pull/78 for some discussion about this.

GuillaumeDIDIER commented 4 years ago

The bug for error code apparently has been fixed on rust nightly, perhaps this reference should be changed to the past. (There used to be a bug with LLVM, but it is now fixed in the latest nightlies)

phil-opp commented 4 years ago

@GuillaumeDIDIER Good catch, thanks for reporting (and sorry for the late reply)! I removed the sentence about the bug in https://github.com/phil-opp/blog_os/pull/711.

midwinter1993 commented 4 years ago

Hi @phil-opp, great post!

I have the same question about the claim all registers are preserved of the x86-interrupt convention as @AtsukiTak.

I tested a code snippet:

extern "x86-interrupt" fn timer_handler(stack_frame: &mut InterruptStackFrame) {
}

and I disassemble it:

; extern "x86-interrupt" fn timer_handler(stack_frame: &mut InterruptStackFrame) {
  20f3a0: 50                            pushq   %rax
  20f3a1: 50                            pushq   %rax
  20f3a2: fc                            cld
  20f3a3: 48 8d 44 24 10                leaq    16(%rsp), %rax
  20f3a8: 48 89 04 24                   movq    %rax, (%rsp)
; }
  20f3ac: 48 83 c4 08                   addq    $8, %rsp
  20f3b0: 58                            popq    %rax
  20f3b1: 48 cf                         iretq

I didn't see pushing the general registers; so, where does the magic happens? Thank you :-P

GuillaumeDIDIER commented 4 years ago

@midwinter1993 LLVM did the magic expected of it. It only saved registers that are clobbered by your function (hence rax). All other register are left unchanged hence there’s no need of saving and restoring them.

midwinter1993 commented 4 years ago

@GuillaumeDIDIER Thank you for clarifying the claim "preserves all registers". :-P

phil-opp commented 4 years ago

@midwinter1993 I just pushed a3eeb1ded7aab5e2b7269ec5073dac87d17ff6ac to make this more clear in the post. Thanks for asking!

LloydoChan commented 4 years ago

Hi Phil, Love the series! With this post, when I use cargo xrun the executable seems to crash when it hits the "x86_64::instructions::interrupts::int3();" line and reboots over and over and qemu seems stuck in a loop. It never reaches the "it did not crash" println statement.

I've checked your branch for this post and verified there's no difference in code, cargo.toml nor the json file.

I've also commented the int3() line out and sprinkled some printlns here and there to see if the "right" functions are exectuted in my interrupts.rs and lib.rs files, so the functions to load the IDT are being hit.

Any ideas?

GuillaumeDIDIER commented 4 years ago

Hi Phil, Love the series! With this post, when I use cargo xrun the executable seems to crash when it hits the "x86_64::instructions::interrupts::int3();" line and reboots over and over and qemu seems stuck in a loop. It never reaches the "it did not crash" println statement.

I've checked your branch for this post and verified there's no difference in code, cargo.toml nor the json file.

I've also commented the int3() line out and sprinkled some printlns here and there to see if the "right" functions are exectuted in my interrupts.rs and lib.rs files, so the functions to load the IDT are being hit.

Any ideas?

The reboot looks like a triple fault (which is not surprising given that we don't have any double fault handler around). I could suggest making use of an actual debugger (bochs with internal debugger is what I've been using), which could allow you to pin point what was the last instruction executed before things blew up (I now there are a few issues with stack alignment that have caused issue in the past if sse support is enabled)

phil-opp commented 4 years ago

Hi @RoidoChan, great to hear that you like the blog!

I've checked your branch for this post and verified there's no difference in code, cargo.toml nor the json file.

Have you tried cloning the repo and running it directly? Sometimes it's a small typo or a file that one didn't think of that is causing the issue. Also, if you have your code online somewhere I can take a look and try to reproduce it.

With this post, when I use cargo xrun the executable seems to crash when it hits the "x86_64::instructions::interrupts::int3();" line and reboots over and over and qemu seems stuck in a loop.

Sounds like something is wrong with your IDT setup. You can try passing -d int to QEMU (through cargo xrun -- -d int) to get a list of exceptions that occur. This trace contains the value of the instruction pointer for each exception, which you can then use to find out which instruction is causing it. A simple way for that is to grep the disassembly of your kernel (e.g. obtained via objdump -d path_to_your_kernel_bin).

Hope this helps! Please let me know what the issue was when you find it.

LloydoChan commented 4 years ago

Hi @GuillaumeDIDIER Thanks for replying!

Hi @phil-opp, I had a real nightmare trying to get cargo objdump -d to work, as It seemed the objdump didn't recognize the x86_64-unknown-none target for some reason, even i explicitly stated the .json file for the target.

I moved some "use" statements around in the .rs files and it started to work for some reason? Worst thing is I didn't do a commit of the buggy version, so I can't diff and see exactly was causing the issue. Sorry!

Thanks so much for your time, and again the blog is great. Learning some comp sci fundamentals I never really understood, despite working professionally in games for some years.

signed up as a Patron! Keep it going!

phil-opp commented 4 years ago

@RoidoChan

I had a real nightmare trying to get cargo objdump -d to work, as It seemed the objdump didn't recognize the x86_64-unknown-none target for some reason, even i explicitly stated the .json file for the target.

Yeah, I found cargo objdump difficult to use too. If you're on Linux or macOS, I recommend just using the system objdump on the ELF executable.

I moved some "use" statements around in the .rs files and it started to work for some reason? Worst thing is I didn't do a commit of the buggy version, so I can't diff and see exactly was causing the issue. Sorry!

No worries, great to hear that it's working now!

Thanks so much for your time, and again the blog is great. Learning some comp sci fundamentals I never really understood, despite working professionally in games for some years.

signed up as a Patron! Keep it going!

It makes me happy to hear that. Thanks a lot for supporting me!