Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cs_buffer. Breaking API change. #2367

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions .github/workflows/CITest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
python-arch: x64,
python-version: '3.6',
build-system: 'cmake',
enable-asan: 'OFF'
}
- {
name: 'ubuntu-22.04 x64 python3.9 make',
Expand All @@ -44,14 +45,16 @@ jobs:
python-arch: x64,
python-version: '3.9',
build-system: 'make',
}
enable-asan: 'OFF'
}
- {
name: 'ubuntu-22.04 x64 python3.9 cmake',
os: ubuntu-22.04,
arch: x64,
python-arch: x64,
python-version: '3.9',
build-system: 'cmake',
enable-asan: 'OFF'
}
- {
name: 'ubuntu-22.04 x64 python3.11 cmake',
Expand All @@ -60,6 +63,16 @@ jobs:
python-arch: x64,
python-version: '3.11',
build-system: 'cmake',
enable-asan: 'OFF'
}
- {
name: 'ubuntu-22.04 x64 python3.11 ASAN',
os: ubuntu-latest,
arch: x64,
python-arch: x64,
python-version: '3.11',
build-system: 'cmake',
enable-asan: 'ON'
}

steps:
Expand Down Expand Up @@ -88,13 +101,15 @@ jobs:

- name: cmake
if: startsWith(matrix.config.build-system, 'cmake')
env:
asan: ${{ matrix.config.enable-asan }}
run: |
mkdir build && cd build
# build static library
cmake -DCAPSTONE_INSTALL=1 -DCMAKE_INSTALL_PREFIX=/usr ..
cmake --build . --config Release
# build shared library
cmake -DCAPSTONE_INSTALL=1 -DBUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX=/usr ..
cmake -DCAPSTONE_INSTALL=1 -DBUILD_SHARED_LIBS=1 -DCMAKE_INSTALL_PREFIX=/usr -DCAPSTONE_BUILD_CSTEST=ON -DENABLE_ASAN=${asan} ..
sudo cmake --build . --config Release --target install
cp libcapstone.* ../
cp libcapstone.* ../tests/
Expand All @@ -106,17 +121,33 @@ jobs:
clang -lcapstone src/test_arm64_compatibility_header.c -o test_arm64_compatibility_header
./test_arm64_compatibility_header

- name: Lower number of KASL randomized address bits
run: |
# Work-around ASAN bug https://github.com/google/sanitizers/issues/1716
sudo sysctl vm.mmap_rnd_bits=28

- name: cstool - reaches disassembler engine
run: |
sh suite/run_invalid_cstool.sh

- name: cstest
- name: cstest (cmake)
if: startsWith(matrix.config.build-system, 'cmake')
run: |
python suite/cstest/cstest_report.py -D -d suite/MC
python suite/cstest/cstest_report.py -D -f suite/cstest/issues.cs
python suite/cstest/cstest_report.py -D -f tests/cs_details/issue.cs

- name: cstest (make)
if: startsWith(matrix.config.build-system, 'make')
run: |
cd suite/cstest && ./build_cstest.sh
python cstest_report.py -D -t build/cstest -d ../MC
python cstest_report.py -D -t build/cstest -f issues.cs; cd ..
python cstest_report.py -D -t build/cstest -f issues.cs
python cstest_report.py -D -t build/cstest -f ../../tests/cs_details/issue.cs
cd ../../

- name: verify python binding
if: matrix.config.enable-asan == 'OFF'
run: |
mkdir -p bindings/python/capstone/lib && cp libcapstone.so.5.* bindings/python/capstone/lib/libcapstone.so
cd bindings/python
Expand All @@ -136,6 +167,7 @@ jobs:
make check

- name: run python binding test
if: matrix.config.enable-asan == 'OFF'
run: |
cp libcapstone.* bindings/python/prebuilt
cd bindings/python
Expand All @@ -144,6 +176,7 @@ jobs:
BUILD_TESTS=no make tests

- name: run cython binding test
if: matrix.config.enable-asan == 'OFF'
run: |
pip install cython
cd bindings/python
Expand Down
27 changes: 23 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ option(CAPSTONE_USE_ARCH_REGISTRATION "Use explicit architecture registration" O
option(CAPSTONE_ARCHITECTURE_DEFAULT "Whether architectures are enabled by default" ON)
option(CAPSTONE_DEBUG "Whether to enable extra debug assertions" OFF)
option(CAPSTONE_INSTALL "Generate install target" ${PROJECT_IS_TOP_LEVEL})
option(ENABLE_ASAN "Enable address sanitizer" OFF)

if (ENABLE_ASAN)
add_definitions(-DASAN_ENABLED)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()

# If building for OSX it's best to allow CMake to handle building both architectures
if(APPLE AND NOT CAPSTONE_BUILD_MACOS_THIN)
Expand Down Expand Up @@ -855,16 +862,28 @@ if(CAPSTONE_BUILD_CSTOOL)
endif()

if(CAPSTONE_BUILD_CSTEST)
find_package(PkgConfig REQUIRED)
pkg_check_modules(CMOCKA REQUIRED IMPORTED_TARGET cmocka)
include(ExternalProject)
ExternalProject_Add(cmocka_ext
PREFIX extern
GIT_REPOSITORY "https://git.cryptomilk.org/projects/cmocka.git"
GIT_TAG "origin/stable-1.1"
CONFIGURE_COMMAND cmake -DBUILD_SHARED_LIBS=OFF ../cmocka_ext/
BUILD_COMMAND cmake --build . --config Release
INSTALL_COMMAND ""
)
set(CMOCKA_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/extern/src/cmocka_ext/include)
set(CMOCKA_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/extern/src/cmocka_ext-build/src/)
add_library(cmocka STATIC IMPORTED)
set_target_properties(cmocka PROPERTIES IMPORTED_LOCATION ${CMOCKA_LIB_DIR}/libcmocka.a)

file(GLOB CSTEST_SRC suite/cstest/src/*.c)
add_executable(cstest ${CSTEST_SRC})
target_link_libraries(cstest PUBLIC capstone PkgConfig::CMOCKA)
add_dependencies(cstest cmocka_ext)
target_link_libraries(cstest PUBLIC capstone cmocka)
target_include_directories(cstest PRIVATE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
${PROJECT_SOURCE_DIR}/suite/cstest/include
${CMOCKA_INCLUDE_DIRS}
${CMOCKA_INCLUDE_DIR}
)

if(CAPSTONE_INSTALL)
Expand Down
2 changes: 2 additions & 0 deletions COMPILE_CMAKE.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ Get CMake for free from http://www.cmake.org.
- CAPSTONE_X86_REDUCE: change this to ON to make X86 binary smaller.
- CAPSTONE_X86_ATT_DISABLE: change this to ON to disable AT&T syntax on x86.
- CAPSTONE_DEBUG: change this to ON to enable extra debug assertions.
- CAPSTONE_BUILD_CSTEST: Build `cstest` in `suite/cstest/`
- ENABLE_ASAN: Compiles Capstone with the address sanitizer.

By default, Capstone use system dynamic memory management, and both DIET and X86_REDUCE
modes are disabled. To use your own memory allocations, turn ON both DIET &
Expand Down
124 changes: 71 additions & 53 deletions bindings/python/capstone/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@ class _cs_insn(ctypes.Structure):
('detail', ctypes.POINTER(_cs_detail)),
)

class _cs_buffer(ctypes.Structure):
_fields_ = (
('private', ctypes.c_void_p),
('insn', ctypes.POINTER(_cs_insn)),
('capacity', ctypes.c_size_t),
('count', ctypes.c_size_t),
)

# callback for SKIPDATA option
CS_SKIPDATA_CALLBACK = ctypes.CFUNCTYPE(ctypes.c_size_t, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t, ctypes.c_size_t, ctypes.c_void_p)

Expand All @@ -525,11 +533,12 @@ def _setup_prototype(lib, fname, restype, *argtypes):
getattr(lib, fname).argtypes = argtypes

_setup_prototype(_cs, "cs_open", ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.POINTER(ctypes.c_size_t))
_setup_prototype(_cs, "cs_buffer_new", ctypes.POINTER(_cs_buffer), ctypes.c_size_t)
_setup_prototype(_cs, "cs_buffer_free", None, ctypes.POINTER(_cs_buffer))
_setup_prototype(_cs, "cs_disasm", ctypes.c_size_t, ctypes.c_size_t, ctypes.POINTER(ctypes.c_char), ctypes.c_size_t, \
ctypes.c_uint64, ctypes.c_size_t, ctypes.POINTER(ctypes.POINTER(_cs_insn)))
ctypes.c_uint64, ctypes.c_size_t, ctypes.POINTER(_cs_buffer))
_setup_prototype(_cs, "cs_disasm_iter", ctypes.c_bool, ctypes.c_size_t, ctypes.POINTER(ctypes.POINTER(ctypes.c_char)), ctypes.POINTER(ctypes.c_size_t), \
ctypes.POINTER(ctypes.c_uint64), ctypes.POINTER(_cs_insn))
_setup_prototype(_cs, "cs_free", None, ctypes.c_void_p, ctypes.c_size_t)
ctypes.POINTER(ctypes.c_uint64), ctypes.POINTER(_cs_buffer))
_setup_prototype(_cs, "cs_close", ctypes.c_int, ctypes.POINTER(ctypes.c_size_t))
_setup_prototype(_cs, "cs_reg_name", ctypes.c_char_p, ctypes.c_size_t, ctypes.c_uint)
_setup_prototype(_cs, "cs_insn_name", ctypes.c_char_p, ctypes.c_size_t, ctypes.c_uint)
Expand Down Expand Up @@ -599,20 +608,21 @@ def cs_disasm_quick(arch, mode, code, offset, count=0):
if status != CS_ERR_OK:
raise CsError(status)

all_insn = ctypes.POINTER(_cs_insn)()
res = _cs.cs_disasm(csh, code, len(code), offset, count, ctypes.byref(all_insn))
if res > 0:
try:
buffer = _cs.cs_buffer_new(0)
try:
res = _cs.cs_disasm(csh, code, len(code), offset, count, buffer)
all_insn = buffer.contents.insn
if res > 0:
for i in range(res):
yield CsInsn(_dummy_cs(csh, arch), all_insn[i])
finally:
_cs.cs_free(all_insn, res)
else:
status = _cs.cs_errno(csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
else:
status = _cs.cs_errno(csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
finally:
_cs.cs_buffer_free(buffer)

status = _cs.cs_close(ctypes.byref(csh))
if status != CS_ERR_OK:
Expand All @@ -639,21 +649,22 @@ def cs_disasm_lite(arch, mode, code, offset, count=0):
if status != CS_ERR_OK:
raise CsError(status)

all_insn = ctypes.POINTER(_cs_insn)()
res = _cs.cs_disasm(csh, code, len(code), offset, count, ctypes.byref(all_insn))
if res > 0:
try:
buffer = _cs.cs_buffer_new(0)
res = _cs.cs_disasm(csh, code, len(code), offset, count, buffer)
all_insn = buffer.contents.insn
try:
if res > 0:
for i in range(res):
insn = all_insn[i]
yield (insn.address, insn.size, insn.mnemonic.decode('ascii'), insn.op_str.decode('ascii'))
finally:
_cs.cs_free(all_insn, res)
else:
status = _cs.cs_errno(csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
else:
status = _cs.cs_errno(csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
finally:
_cs.cs_buffer_free(buffer)

status = _cs.cs_close(ctypes.byref(csh))
if status != CS_ERR_OK:
Expand Down Expand Up @@ -1214,7 +1225,6 @@ def group_name(self, group_id, default=None):

# Disassemble binary & return disassembled instructions in CsInsn objects
def disasm(self, code, offset, count=0):
all_insn = ctypes.POINTER(_cs_insn)()
'''if not _python2:
print(code)
code = code.encode()
Expand All @@ -1226,19 +1236,21 @@ def disasm(self, code, offset, count=0):
code = ctypes.byref(ctypes.c_char.from_buffer(view))
elif not isinstance(code, bytes):
code = view.tobytes()
res = _cs.cs_disasm(self.csh, code, size, offset, count, ctypes.byref(all_insn))
if res > 0:
try:
buffer = _cs.cs_buffer_new(0)
res = _cs.cs_disasm(self.csh, code, size, offset, count, buffer)
all_insn = buffer.contents.insn
try:
if res > 0:
for i in range(res):
yield CsInsn(self, all_insn[i])
finally:
_cs.cs_free(all_insn, res)
else:
status = _cs.cs_errno(self.csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
else:
status = _cs.cs_errno(self.csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
finally:
_cs.cs_buffer_free(buffer)

# This function matches the cs_disasm_iter implementation which
# *should* be much faster via the C API due to pre-allocating
Expand All @@ -1264,8 +1276,13 @@ def disasm_iter(self, code, offset):
# the typical auto conversion, so we have to cast it here.
code = ctypes.cast(code, ctypes.POINTER(ctypes.c_char))
address = ctypes.c_uint64(offset)
while _cs.cs_disasm_iter(self.csh, ctypes.byref(code), ctypes.byref(size), ctypes.byref(address), ctypes.byref(insn)):
yield (insn.address, insn.size, insn.mnemonic.decode('ascii'), insn.op_str.decode('ascii'))
buffer = _cs.cs_buffer_new(0)
try:
while _cs.cs_disasm_iter(self.csh, ctypes.byref(code), ctypes.byref(size), ctypes.byref(address), buffer):
insn = buffer.contents.insn[0]
yield (insn.address, insn.size, insn.mnemonic.decode('ascii'), insn.op_str.decode('ascii'))
finally:
_cs.cs_buffer_free(buffer)

# Light function to disassemble binary. This is about 20% faster than disasm() because
# unlike disasm(), disasm_lite() only return tuples of (address, size, mnemonic, op_str),
Expand All @@ -1275,28 +1292,29 @@ def disasm_lite(self, code, offset, count=0):
# Diet engine cannot provide @mnemonic & @op_str
raise CsError(CS_ERR_DIET)

all_insn = ctypes.POINTER(_cs_insn)()
size = len(code)
# Pass a bytearray by reference
view = memoryview(code)
if not view.readonly:
code = ctypes.byref(ctypes.c_char.from_buffer(view))
elif not isinstance(code, bytes):
code = view.tobytes()
res = _cs.cs_disasm(self.csh, code, size, offset, count, ctypes.byref(all_insn))
if res > 0:
try:
buffer = _cs.cs_buffer_new(0)
res = _cs.cs_disasm(self.csh, code, size, offset, count, buffer)
all_insn = buffer.contents.insn
try:
if res > 0:
for i in range(res):
insn = all_insn[i]
yield (insn.address, insn.size, insn.mnemonic.decode('ascii'), insn.op_str.decode('ascii'))
finally:
_cs.cs_free(all_insn, res)
else:
status = _cs.cs_errno(self.csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
else:
status = _cs.cs_errno(self.csh)
if status != CS_ERR_OK:
raise CsError(status)
return
yield
finally:
_cs.cs_buffer_free(buffer)


# print out debugging info
Expand Down
Loading
Loading