qiling icon indicating copy to clipboard operation
qiling copied to clipboard

Cortex-M Vector Table Offset Register not used during mcu emulation

Open antcpl opened this issue 10 months ago • 1 comments

Describe the bug When emulating an mcu based on the cortex-m, Qiling never uses the value stored in the Vector Table Offset Register. This register is used to indicate an offset for the base address of the vector table. This produces a bug when emulating a firmware that is using this register to indicate an offset to use its vector table. In my case the vector table is used in the firmware to fetch interrupt handler, the bug makes the firmware trying to fetch interrupt handlers from wrong address.

Sample Code

ql = Qiling(["./toto.elf"],
                archtype=QL_ARCH.CORTEX_M, ostype=QL_OS.MCU, env=stm32f103, verbose=QL_VERBOSE.DISABLED)

    ql.hw.create('scb')
    ql.hw.create('gpioa')
    ql.hw.create('usart2').watch()
    ql.hw.create('rcc')
    ql.hw.create('afio')
    ql.hw.create('exti')
    ql.hw.create('gpioc')

    ql.hw.show_info()

    ql.hw.usart2.send("totototo".encode())

    ql.run(count=1000000)

Expected behavior Just take into account the value present in the register.

Additional context Tested on the dev branch. This register is laying in the SCB part of the CPU memory, as shown in the code above once scb hardware added to the emulation, the memory is perfectly handled, read and write to it works fine (checked by using DISASM debug log).

Suggested correction For me this was problematic when the firmware uses interruption so here is how I corrected the problem. It has been tested and worked perfectly but I'm not sure this is the right place to implement this correction plus I've hardcoded the address which is very ugly. In qiling/arch/cortex_m.py in interrupt_handler function :

def interrupt_handler(self, ql: Qiling, intno: int):
        basepri = self.regs.basepri & 0xf0

        if basepri and basepri <= ql.hw.nvic.get_priority(intno):
            return

        if intno > IRQ.HARD_FAULT and (self.regs.primask & 0x1):
            return

        if intno != IRQ.NMI and (self.regs.faultmask & 0x1):
            return

        if ql.verbose >= QL_VERBOSE.DISASM:
            ql.log.debug(f'Handle the intno: {intno}')

        with QlInterruptContext(ql):
            isr = intno + 16
            offset = isr * 4
            
            # ============= personnal modifications ============= 
            #Here qiling doesn't care about the SCB_VTOR which is not normal for the cortex M 
            SCB_VTOR = int.from_bytes(ql.mem.read(0xe000ed08,4), byteorder='little')

            entry = ql.mem.read_ptr(offset + SCB_VTOR)
            # ======================================= 

            exc_return = 0xFFFFFFFD if self.using_psp() else 0xFFFFFFF9

            self.regs.write('ipsr', isr)
            self.regs.write('pc', entry)
            self.regs.write('lr', exc_return)

            ql.log.debug(hex(self.effective_pc))

            self.uc.emu_start(self.effective_pc, 0, 0, 0xffffff)

antcpl avatar Mar 18 '25 10:03 antcpl

Maybe a profile configuration would make sense here, but I am not sure. Tagging @cla7aye15I4nd for awareness.

In the meantime, you might want to replace the int.from_bytes + ql.mem.read with just ql.mem.read_ptr

elicn avatar Mar 18 '25 11:03 elicn