diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..230281c2 --- /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/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; } 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):