From f974c659e9455d9bb4db5a20e262ed181999620d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:27:53 +0800 Subject: [PATCH 1/4] Add support for missing IL operations in DebuggerController::ComputeExprValue (#802) * Initial plan * Add support for arithmetic and function call IL operations Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> * Complete implementation of missing IL operations including conditionals Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- core/debuggercontroller.cpp | 345 ++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index ad6d4387..3bf5c93c 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -3033,6 +3033,119 @@ bool DebuggerController::ComputeExprValue(const LowLevelILInstruction &instr, in value &= sizeMask; return true; } + case LLIL_MUL: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + value = left * right; + value &= sizeMask; + return true; + } + case LLIL_MULU_HI: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + auto result = left * right; + value = result >> (instr.size * 8); + value &= sizeMask; + return true; + } + case LLIL_MULS_HI: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + // Sign extend operands for signed multiplication + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned * rightSigned; + value = result >> (instr.size * 8); + value &= sizeMask; + return true; + } + case LLIL_DIVU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left / right; + value &= sizeMask; + return true; + } + case LLIL_DIVS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed division + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned / rightSigned; + value = result & sizeMask; + return true; + } + case LLIL_MODU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left % right; + value &= sizeMask; + return true; + } + case LLIL_MODS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed modulo + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned % rightSigned; + value = result & sizeMask; + return true; + } + case LLIL_ROL: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + auto shift = GetActualShift(right, instr.size); + auto bits = instr.size * 8; + shift %= bits; // Normalize rotation amount + value = ((left << shift) | (left >> (bits - shift))) & sizeMask; + return true; + } + case LLIL_ROR: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + auto shift = GetActualShift(right, instr.size); + auto bits = instr.size * 8; + shift %= bits; // Normalize rotation amount + value = ((left >> shift) | (left << (bits - shift))) & sizeMask; + return true; + } case LLIL_NEG: { if (!ComputeExprValue(instr.GetSourceExpr(), left)) @@ -3163,6 +3276,33 @@ bool DebuggerController::ComputeExprValue(const LowLevelILInstruction &instr, in value = GetValueFromComparison(instr.operation, left, right, instr.size); return true; + case LLIL_CALL: + { + // For CALL operations, we compute the destination address + // The actual function call would be executed by the debugger, not here + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case LLIL_TAILCALL: + { + // For TAILCALL operations, we compute the destination address + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case LLIL_IF: + { + // For IF operations, we evaluate the condition and return 0 or 1 + if (!ComputeExprValue(instr.GetConditionExpr(), left)) + return false; + // Convert to boolean (0 or 1) + value = (left != 0) ? 1 : 0; + return true; + } + default: break; } @@ -3485,6 +3625,95 @@ bool DebuggerController::ComputeExprValue(const MediumLevelILInstruction &instr, value &= sizeMask; return true; } + case MLIL_MUL: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + value = left * right; + value &= sizeMask; + return true; + } + case MLIL_MULU_HI: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + auto result = left * right; + value = result >> (instr.size * 8); + value &= sizeMask; + return true; + } + case MLIL_MULS_HI: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + // Sign extend operands for signed multiplication + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned * rightSigned; + value = result >> (instr.size * 8); + value &= sizeMask; + return true; + } + case MLIL_DIVU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left / right; + value &= sizeMask; + return true; + } + case MLIL_DIVS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed division + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned / rightSigned; + value = result & sizeMask; + return true; + } + case MLIL_MODU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left % right; + value &= sizeMask; + return true; + } + case MLIL_MODS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed modulo + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned % rightSigned; + value = result & sizeMask; + return true; + } case MLIL_NEG: { if (!ComputeExprValue(instr.GetSourceExpr(), left)) @@ -3595,6 +3824,32 @@ bool DebuggerController::ComputeExprValue(const MediumLevelILInstruction &instr, value = GetValueFromComparison(instr.operation, left, right, instr.size); return true; + case MLIL_CALL: + { + // For CALL operations, we compute the destination address + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case MLIL_TAILCALL: + { + // For TAILCALL operations, we compute the destination address + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case MLIL_IF: + { + // For IF operations, we evaluate the condition and return 0 or 1 + if (!ComputeExprValue(instr.GetConditionExpr(), left)) + return false; + // Convert to boolean (0 or 1) + value = (left != 0) ? 1 : 0; + return true; + } + default: return false; } @@ -3790,6 +4045,70 @@ bool DebuggerController::ComputeExprValue(const HighLevelILInstruction &instr, i value &= sizeMask; return true; } + case HLIL_MUL: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + value = left * right; + value &= sizeMask; + return true; + } + case HLIL_DIVU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left / right; + value &= sizeMask; + return true; + } + case HLIL_DIVS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed division + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned / rightSigned; + value = result & sizeMask; + return true; + } + case HLIL_MODU: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + value = left % right; + value &= sizeMask; + return true; + } + case HLIL_MODS: + { + if (!ComputeExprValue(instr.GetLeftExpr(), left)) + return false; + if (!ComputeExprValue(instr.GetRightExpr(), right)) + return false; + if (right == 0) + return false; // Division by zero + // Sign extend operands for signed modulo + auto leftSigned = SignExtend(left, instr.size, 64); + auto rightSigned = SignExtend(right, instr.size, 64); + auto result = leftSigned % rightSigned; + value = result & sizeMask; + return true; + } case HLIL_NEG: { if (!ComputeExprValue(instr.GetSourceExpr(), left)) @@ -3900,6 +4219,32 @@ bool DebuggerController::ComputeExprValue(const HighLevelILInstruction &instr, i value = GetValueFromComparison(instr.operation, left, right, instr.size); return true; + case HLIL_CALL: + { + // For CALL operations, we compute the destination address + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case HLIL_TAILCALL: + { + // For TAILCALL operations, we compute the destination address + if (!ComputeExprValue(instr.GetDestExpr(), left)) + return false; + value = left; + return true; + } + case HLIL_IF: + { + // For IF operations, we evaluate the condition and return 0 or 1 + if (!ComputeExprValue(instr.GetConditionExpr(), left)) + return false; + // Convert to boolean (0 or 1) + value = (left != 0) ? 1 : 0; + return true; + } + default: return false; } From 3afdc4b1420a8039c3dd079b279c046d36fe9d35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 03:33:23 +0000 Subject: [PATCH 2/4] Initial plan From 4e9ca789e3e3ea5a2764086eb244ab6a76044d89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 03:45:20 +0000 Subject: [PATCH 3/4] Add CI test for remote debugging on localhost Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- .github/workflows/test.yml | 82 ++++++++++++++++++++++++++++++++++++ docs/ci-remote-debug-test.md | 45 ++++++++++++++++++++ test/debugger_test.py | 68 ++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 docs/ci-remote-debug-test.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..b6be7c8c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,82 @@ +name: CI Tests + +on: + push: + branches: [ dev, main ] + pull_request: + branches: [ dev, main ] + +jobs: + test-remote-debugging: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y lldb + + - name: Test Remote Debugging Infrastructure + run: | + cd test + python3 -c " +import os +import sys +import time +import platform +import subprocess +import socket + +def find_free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port + +port = find_free_port() +host = '127.0.0.1' + +# Use helloworld binary for testing +fpath = 'binaries/Linux-x86_64/helloworld' +if not os.path.exists(fpath): + print(f'Test binary not found: {fpath}') + sys.exit(1) + +server_cmd = ['lldb-server', 'gdbserver', f'{host}:{port}', fpath] + +try: + print(f'Starting lldb-server on {host}:{port} with {fpath}') + server_process = subprocess.Popen(server_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(2) + + if server_process.poll() is not None: + stdout, stderr = server_process.communicate() + print(f'lldb-server failed: stdout={stdout.decode()}, stderr={stderr.decode()}') + sys.exit(1) + + # Test connection + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(10) + s.connect((host, port)) + s.sendall(b'+\\$qSupported#37') + response = s.recv(1024) + + if response: + print('✓ Remote debugging test successful: Connected to lldb-server on localhost') + else: + print('✗ No response from lldb-server') + sys.exit(1) + +finally: + if server_process and server_process.poll() is None: + server_process.terminate() + server_process.wait(timeout=5) +" \ No newline at end of file diff --git a/docs/ci-remote-debug-test.md b/docs/ci-remote-debug-test.md new file mode 100644 index 00000000..30bc2a1c --- /dev/null +++ b/docs/ci-remote-debug-test.md @@ -0,0 +1,45 @@ +# CI Remote Debugging Test + +This document describes the CI test for remote debugging functionality. + +## Overview + +The CI test validates the remote debugging infrastructure by: + +1. Starting an `lldb-server` process on localhost (127.0.0.1) +2. Establishing a connection to the server using GDB remote protocol +3. Verifying basic communication with the debug server + +## Test Details + +- **Platform**: Linux (Ubuntu) with lldb installed +- **Binary**: Uses the `helloworld` test binary from `test/binaries/Linux-x86_64/` +- **Protocol**: GDB Remote Serial Protocol via `lldb-server gdbserver` +- **Scope**: Infrastructure test - validates that remote debugging setup works + +## Running the Test + +The test runs automatically in GitHub Actions CI on every push and pull request. + +To run manually: + +```bash +cd test +python3 -c " +# Test script content from .github/workflows/test.yml +" +``` + +## Implementation + +The test is implemented in: +- `.github/workflows/test.yml` - GitHub Actions workflow +- `test/debugger_test.py` - Additional unit test (when Binary Ninja dependencies are available) + +## Purpose + +This test ensures that: +- Remote debugging infrastructure components work correctly +- `lldb-server` can start and accept connections +- Basic GDB remote protocol communication functions +- CI environment can validate remote debugging changes \ No newline at end of file diff --git a/test/debugger_test.py b/test/debugger_test.py index f9ce2809..16ec1217 100644 --- a/test/debugger_test.py +++ b/test/debugger_test.py @@ -351,6 +351,74 @@ def test_attach(self): dbg.quit_and_wait() + @unittest.skipIf(platform.system() != 'Linux', 'Remote debugging test only runs on Linux with lldb-server') + def test_remote_debug_localhost(self): + """Test remote debugging infrastructure by starting lldb-server on localhost""" + import socket + import time + + # This test verifies the remote debugging infrastructure works by: + # 1. Starting an lldb-server on localhost + # 2. Verifying it can accept connections + # 3. Testing basic GDB remote protocol communication + + # Find an available port + def find_free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port + + port = find_free_port() + host = '127.0.0.1' + + # Start lldb-server process in gdbserver mode + fpath = name_to_fpath('helloworld', self.arch) + server_cmd = ['lldb-server', 'gdbserver', f'{host}:{port}', fpath] + + server_process = None + try: + # Start the lldb-server + server_process = subprocess.Popen(server_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Give the server time to start + time.sleep(2) + + # Check if server is still running + if server_process.poll() is not None: + stdout, stderr = server_process.communicate() + self.fail(f"lldb-server failed to start: stdout={stdout.decode()}, stderr={stderr.decode()}") + + # Test basic connection to the server + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(10) + s.connect((host, port)) + + # Send a basic GDB packet to test communication + # '+' acknowledges packets, '$qSupported#37' is a basic query + s.sendall(b'+$qSupported#37') + + # Read response + response = s.recv(1024) + self.assertIsNotNone(response) + self.assertGreater(len(response), 0) + + # Success - we established a remote debugging connection! + print("Remote debugging test successful: Connected to lldb-server on localhost") + + finally: + # Clean up server process + if server_process and server_process.poll() is None: + server_process.terminate() + try: + server_process.wait(timeout=5) + except subprocess.TimeoutExpired: + server_process.kill() + server_process.wait() + @unittest.skipIf(platform.machine() not in ['arm64', 'aarch64'], "Only run arm64 tests on arm Mac or Linux") class DebuggerArm64Test(DebuggerAPI): From 17f1a74ab7e54383803cbd9331a9bc5739021a95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 03:46:55 +0000 Subject: [PATCH 4/4] Fix YAML formatting in CI workflow for remote debugging test Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- .github/workflows/test.yml | 94 +++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6be7c8c..230281c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,56 +27,56 @@ jobs: run: | cd test python3 -c " -import os -import sys -import time -import platform -import subprocess -import socket + import os + import sys + import time + import platform + import subprocess + import socket -def find_free_port(): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - s.listen(1) - port = s.getsockname()[1] - return port + def find_free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port -port = find_free_port() -host = '127.0.0.1' + port = find_free_port() + host = '127.0.0.1' -# Use helloworld binary for testing -fpath = 'binaries/Linux-x86_64/helloworld' -if not os.path.exists(fpath): - print(f'Test binary not found: {fpath}') - sys.exit(1) + # Use helloworld binary for testing + fpath = 'binaries/Linux-x86_64/helloworld' + if not os.path.exists(fpath): + print(f'Test binary not found: {fpath}') + sys.exit(1) -server_cmd = ['lldb-server', 'gdbserver', f'{host}:{port}', fpath] + server_cmd = ['lldb-server', 'gdbserver', f'{host}:{port}', fpath] -try: - print(f'Starting lldb-server on {host}:{port} with {fpath}') - server_process = subprocess.Popen(server_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - time.sleep(2) - - if server_process.poll() is not None: - stdout, stderr = server_process.communicate() - print(f'lldb-server failed: stdout={stdout.decode()}, stderr={stderr.decode()}') - sys.exit(1) - - # Test connection - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(10) - s.connect((host, port)) - s.sendall(b'+\\$qSupported#37') - response = s.recv(1024) - - if response: - print('✓ Remote debugging test successful: Connected to lldb-server on localhost') - else: - print('✗ No response from lldb-server') - sys.exit(1) + try: + print(f'Starting lldb-server on {host}:{port} with {fpath}') + server_process = subprocess.Popen(server_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(2) + + if server_process.poll() is not None: + stdout, stderr = server_process.communicate() + print(f'lldb-server failed: stdout={stdout.decode()}, stderr={stderr.decode()}') + sys.exit(1) -finally: - if server_process and server_process.poll() is None: - server_process.terminate() - server_process.wait(timeout=5) -" \ No newline at end of file + # Test connection + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(10) + s.connect((host, port)) + s.sendall(b'+\\$qSupported#37') + response = s.recv(1024) + + if response: + print('✓ Remote debugging test successful: Connected to lldb-server on localhost') + else: + print('✗ No response from lldb-server') + sys.exit(1) + + finally: + if server_process and server_process.poll() is None: + server_process.terminate() + server_process.wait(timeout=5) + " \ No newline at end of file