unicorn icon indicating copy to clipboard operation
unicorn copied to clipboard

Python callbacks suppress KeyboardInterrupt exceptions

Open DavidBuchanan314 opened this issue 9 months ago • 3 comments

from unicorn import *
from unicorn.x86_const import *

def instruction_callback(uc, a, b, c):
	print("hello, callback")
	#raise Exception("this works as expected")
	raise KeyboardInterrupt("this gets ignored")

mu = Uc(UC_ARCH_X86, UC_MODE_32)
mu.mem_map(0x1000, 0x1000)
mu.mem_write(0x1000, b"\xeb\xfe") # infinite loop
mu.hook_add(UC_HOOK_CODE, instruction_callback)
mu.emu_start(0x1000, -1, count=5)
print("ended without errors (unexpected!)")

Here's a simple example program.

If the commented line is uncommented, the program works as I'd expect/like - emu_start() raises an exception and the final print() does not execute.

But as written (i.e. with the callback raising a KeyboardInterrupt exception), it goes like this:

hello, callback
Exception ignored on calling ctypes callback function: <bound method Uc._hookcode_cb of <unicorn.unicorn.Uc object at 0xffff42acbf50>>
Traceback (most recent call last):
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 392, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 663, in _hookcode_cb
    cb(self, address, size, data)
  File "/home/david/re/TDS864A/TDS684A/unicorn_hook_poc.py", line 7, in instruction_callback
    raise KeyboardInterrupt("this gets ignored")
KeyboardInterrupt: this gets ignored
hello, callback
Exception ignored on calling ctypes callback function: <bound method Uc._hookcode_cb of <unicorn.unicorn.Uc object at 0xffff42acbf50>>
Traceback (most recent call last):
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 392, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 663, in _hookcode_cb
    cb(self, address, size, data)
  File "/home/david/re/TDS864A/TDS684A/unicorn_hook_poc.py", line 7, in instruction_callback
    raise KeyboardInterrupt("this gets ignored")
KeyboardInterrupt: this gets ignored
hello, callback
Exception ignored on calling ctypes callback function: <bound method Uc._hookcode_cb of <unicorn.unicorn.Uc object at 0xffff42acbf50>>
Traceback (most recent call last):
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 392, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 663, in _hookcode_cb
    cb(self, address, size, data)
  File "/home/david/re/TDS864A/TDS684A/unicorn_hook_poc.py", line 7, in instruction_callback
    raise KeyboardInterrupt("this gets ignored")
KeyboardInterrupt: this gets ignored
hello, callback
Exception ignored on calling ctypes callback function: <bound method Uc._hookcode_cb of <unicorn.unicorn.Uc object at 0xffff42acbf50>>
Traceback (most recent call last):
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 392, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 663, in _hookcode_cb
    cb(self, address, size, data)
  File "/home/david/re/TDS864A/TDS684A/unicorn_hook_poc.py", line 7, in instruction_callback
    raise KeyboardInterrupt("this gets ignored")
KeyboardInterrupt: this gets ignored
hello, callback
Exception ignored on calling ctypes callback function: <bound method Uc._hookcode_cb of <unicorn.unicorn.Uc object at 0xffff42acbf50>>
Traceback (most recent call last):
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 392, in wrapper
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/david/.local/lib/python3.12/site-packages/unicorn/unicorn.py", line 663, in _hookcode_cb
    cb(self, address, size, data)
  File "/home/david/re/TDS864A/TDS684A/unicorn_hook_poc.py", line 7, in instruction_callback
    raise KeyboardInterrupt("this gets ignored")
KeyboardInterrupt: this gets ignored
ended without errors (unexpected!)

I can't really figure out why this is happening. It's unfortunate because in real-world code it results in a program that can't easily be killed via ctrl+C.

DavidBuchanan314 avatar Mar 11 '25 18:03 DavidBuchanan314

I think this is where the "exception ignored" message is coming from https://github.com/python/cpython/blob/8b1edae93a05cc90c5b8c5c935f3753aca938ccf/Modules/_ctypes/callbacks.c#L212-L217

DavidBuchanan314 avatar Mar 11 '25 19:03 DavidBuchanan314

I'm still not quite sure what's going on, but I have a workaround, which is to catch the KeyboardInterrupt exception in the wrapper and re-raise it as a regular Exception:

from unicorn import *
from unicorn.x86_const import *
from functools import wraps

def my_wrapper(f):
	@wraps(f)
	def wrapper(*args, **kwds):
		try:
			return f(*args, **kwds)
		except KeyboardInterrupt:
			raise Exception("KeyboardInterrupt raised in callback")
	return wrapper

@my_wrapper
def instruction_callback(uc, a, b, c):
	print("hello, callback")
	#raise Exception("this works as expected")
	raise KeyboardInterrupt("this gets ignored")

mu = Uc(UC_ARCH_X86, UC_MODE_32)
mu.mem_map(0x1000, 0x1000)
mu.mem_write(0x1000, b"\xeb\xfe") # infinite loop
mu.hook_add(UC_HOOK_CODE, instruction_callback)
mu.emu_start(0x1000, -1, count=5)
print("ended without errors (unexpected!)")

DavidBuchanan314 avatar Mar 11 '25 19:03 DavidBuchanan314

This is expected and documented here: https://github.com/unicorn-engine/unicorn/wiki/FAQ#keyboardinterrupt-is-not-raised-during-ucemu_start

wtdcode avatar Mar 12 '25 05:03 wtdcode