3
\$\begingroup\$

I wrote this class to use a C++ function as hardware interrupt handler. The idea is to have a single entry point where I can switch stacks, and call a chain of std::function objects to handle the interrupt.

It works... but I think it's a pretty ugly hack. Is there anything I could improve here?

class __attribute__((packed)) interrupt_wrapper
{
    using function_ptr = void(*)(unsigned);
    unsigned int_vector;            // [eax-0x1C]
    selector ds;                    // [eax-0x18]
    selector es;                    // [eax-0x16]
    selector fs;                    // [eax-0x14]
    selector gs;                    // [eax-0x12]
    function_ptr entry_point;       // [eax-0x10]
    std::array<byte, 0x40> code;    // [eax-0x0C]

public:
    interrupt_wrapper(unsigned vec, function_ptr f) : int_vector(vec), entry_point(f)
    {
        byte* start;
        std::size_t size;
        asm volatile (
            ".intel_syntax noprefix;"
            "jmp interrupt_wrapper_end%=;"
            // --- \/\/\/\/\/\/ --- //
            "interrupt_wrapper_begin%=:;"   // On entry, the only known register is CS.
            "push ds; push es; push fs; push gs; pusha;"    // 7 bytes
            "call get_eip%=;"  // call near/relative (E8)   // 5 bytes
            "get_eip%=: pop eax;"           // Pop EIP into EAX and use it to find our variables
            "mov ds, cs:[eax-0x18];"        // Restore segment registers
            "mov es, cs:[eax-0x16];"        
            "mov fs, cs:[eax-0x14];"
            "mov gs, cs:[eax-0x12];"
            "push cs:[eax-0x1C];"           // Pass our interrupt number along
            "call cs:[eax-0x10];"           // Call the entry point
            "add esp, 4;"
            "popa; pop gs; pop fs; pop es; pop ds;"
            "sti;"                          // IRET is not guaranteed to set the interrupt flag.
            "iret;"
            "interrupt_wrapper_end%=:;"
            // --- /\/\/\/\/\/\ --- //
            "mov %0, offset interrupt_wrapper_begin%=;"
            "mov %1, offset interrupt_wrapper_end%=;"
            "sub %1, %0;"                   // size = end - begin
            ".att_syntax prefix"
            : "=r" (start)
            , "=r" (size));
        assert(size <= code.size());

        auto* ptr = memory_descriptor(get_cs(), start).get_ptr<byte>(); // Get near pointer to cs:[start]
        std::copy_n(ptr, size, code.data());

        asm volatile (
            ".intel_syntax noprefix;"
            "mov ax, ds;"
            "mov bx, es;"
            "mov cx, fs;"
            "mov dx, gs;"
            ".att_syntax prefix"
            : "=a" (ds)
            , "=b" (es)
            , "=c" (fs)
            , "=d" (gs));
    }

    auto get_ptr(selector cs) { return far_ptr32 { cs, reinterpret_cast<std::size_t>(code.data()) }; }
};
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

We're missing some pieces, such as the memory_descriptor and far_ptr32 classes and get_cs function and I guessed at the definitions of selector and byte, but with that said, here's what I found that may help you improve your code.

Make the constructor noexcept

It's unlikely that an exception within the constructor would be a welcome event. For that reason, your constructor should be declared noexcept.

Make function_ptr noexcept

For similar reasons, I'd suggest making function_ptr also noexcept:

using function_ptr = void(*)(unsigned) noexcept;

Be careful with interrupt flags

Instead of disabling all interrupts using sti, it might be better to restore the interrupt flag to whatever it had been. Although the interrupt could not have been called by hardware if the interrupt flag had been reset (that is, interrupts disabled) but it's not uncommon to chain interrupts by having one hardware interrupt chain to another via explicit call. In that circumstance, the hardware flag should not be re-enabled lest the interrupt chain be incorrectly interrupted.

\$\endgroup\$
8
  • \$\begingroup\$ Thanks, these are some good points. I didn't know you could mark C-style function pointers as noexcept, too. The memory_descriptor and far_ptr32 classes are not really relevant in this question, I think. The first describes a linear memory region and provides near-pointer access, among other things. the second describes a far-pointer (selector and offset). byte and selector are, as you may have guessed, aliases for std::uint8_t and std::uint16_t, respectively. \$\endgroup\$ Mar 9, 2016 at 22:47
  • \$\begingroup\$ I do still think it's a good idea to set the interrupt flag unconditionally on return. Think about it: if interrupts were disabled, the interrupt handler would have never been called in the first place. \$\endgroup\$ Mar 9, 2016 at 22:48
  • \$\begingroup\$ The interrupt handler would not have been called by a hardware interrupt but there is still the possibility that it was invoked (e.g. by interrupt chaining) by software. \$\endgroup\$
    – Edward
    Mar 9, 2016 at 22:53
  • \$\begingroup\$ If it was chained from another hardware interrupt, and mine is the last in the chain, wouldn't my handler have the responsibility to set the flag again? I just looked it up and the OS documentation also recommends to sti before iret. This doesn't really improve my headache... \$\endgroup\$ Mar 9, 2016 at 23:37
  • \$\begingroup\$ It's not clear why you'd assume your interrupt is the last in the chain. Interrupt chaining doesn't necessarily require the participation of the individual interrupt handlers. \$\endgroup\$
    – Edward
    Mar 9, 2016 at 23:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.