Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Checks: >
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-owning-memory,
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/generate-coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ if ! ls caesar-*.profraw 1> /dev/null 2>&1; then
fi

echo "Merging profile data..."
/usr/bin/llvm-profdata-21 merge -sparse caesar-*.profraw -o caesar.profdata
llvm-profdata merge -sparse caesar-*.profraw -o caesar.profdata

echo "Generating coverage report (text summary)..."
/usr/bin/llvm-cov-21 report ./caesar_test \
llvm-cov report ./caesar_test \
-instr-profile=caesar.profdata \
-ignore-filename-regex='test/.*' \
-ignore-filename-regex='.*/Catch2/.*'

echo "Generating detailed HTML report..."
/usr/bin/llvm-cov-21 show ./caesar_test \
llvm-cov show ./caesar_test \
-instr-profile=caesar.profdata \
-format=html \
-output-dir=coverage-report \
Expand All @@ -34,7 +34,7 @@ echo "Generating detailed HTML report..."

echo "Generating per-file coverage summary..."
echo "### Per-File Coverage Summary" > coverage-summary.txt
/usr/bin/llvm-cov-21 report ./caesar_test \
llvm-cov report ./caesar_test \
-instr-profile=caesar.profdata \
-ignore-filename-regex='test/.*' \
-ignore-filename-regex='.*/Catch2/.*' \
Expand Down
29 changes: 8 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
runs-on: macos-26

steps:
- uses: actions/checkout@v4
Expand All @@ -24,16 +24,8 @@ jobs:

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build ccache
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21 all
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/clang-scan-deps clang-scan-deps /usr/bin/clang-scan-deps-21 100
sudo update-alternatives --install /usr/bin/llvm-profdata llvm-profdata /usr/bin/llvm-profdata-21 100
sudo update-alternatives --install /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-21 100
brew install cmake ninja ccache llvm
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH

- name: Setup ccache
uses: hendrikmuhs/ccache-action@v1.2
Expand All @@ -51,8 +43,8 @@ jobs:
- name: Configure CMake
run: cmake -B build --preset debug -DENABLE_COVERAGE=ON
env:
CC: /usr/bin/clang-21
CXX: /usr/bin/clang++-21
CC: clang
CXX: clang++
CMAKE_CXX_COMPILER_LAUNCHER: ccache

- name: Build
Expand Down Expand Up @@ -81,7 +73,7 @@ jobs:

lint:
name: Lint
runs-on: ubuntu-latest
runs-on: macos-26
needs: build

steps:
Expand All @@ -91,13 +83,8 @@ jobs:

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21 all
sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-21 100
sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-21 100
brew install cmake ninja llvm
echo "$(brew --prefix llvm)/bin" >> $GITHUB_PATH

- name: Download build artifacts
uses: actions/download-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions src/cmd/environment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Environment {
this->define("run", std::make_shared<RunFn>(RunFn()));
this->define("resume", std::make_shared<ContinueFn>(ContinueFn()));
this->define("target", std::make_shared<TargetFn>(TargetFn()));
this->define("register", std::make_shared<RegisterFn>(RegisterFn()));
}

public:
Expand Down
44 changes: 37 additions & 7 deletions src/cmd/stdlib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "callable.hpp"
#include "cmd/object.hpp"
#include "cmd/util.hpp"
#include "core/platform.hpp"
#include "core/target.hpp"
#include "error.hpp"
#include "expected.hpp"
Expand Down Expand Up @@ -52,7 +53,7 @@ class BreakpointFn : public Callable {
private:
static Object rmOrToggleBreakpoint(const std::vector<std::string>& args,
bool toggle = false) {
Expected<u64, std::string> addrRes = strToAddr(args[0]);
Expected<u64, std::string> addrRes = detail::strToAddr(args[0]);
if (!addrRes) return addrRes.error();
u64 addr = *addrRes;

Expand Down Expand Up @@ -81,7 +82,7 @@ class BreakpointFn : public Callable {
if (breakpoints.empty()) return "No breakpoints set!";

for (const auto& [k, v] : breakpoints) {
retStr += std::format("Breakpoint @ {} ({})\n", toHex(k),
retStr += std::format("Breakpoint @ {} ({})\n", detail::toHex(k),
breakpoints[k].enabled);
}

Expand All @@ -90,15 +91,15 @@ class BreakpointFn : public Callable {
};

FnPtr set = [](const std::vector<std::string>& args) -> Object {
Expected<u64, std::string> addrRes = strToAddr(args[0]);
Expected<u64, std::string> addrRes = detail::strToAddr(args[0]);
if (!addrRes) return addrRes.error();
const u64 addr = *addrRes;

const i32 bpRes = Context::getTarget()->setBreakpoint(addr);
if (bpRes != 0)
return "Error setting breakpoint!";
else
return std::format("Breakpoint set at: {}", toHex(addr));
return std::format("Breakpoint set at: {}", detail::toHex(addr));
};

FnPtr remove = [](const std::vector<std::string>& args) -> Object {
Expand Down Expand Up @@ -136,7 +137,7 @@ class BreakpointFn : public Callable {

class RunFn : public Callable {
private:
static CStringArray concatElems(const std::vector<Object>& v) {
static detail::CStringArray concatElems(const std::vector<Object>& v) {
auto ensureStr = [](const auto& obj) -> std::string {
using T = std::decay_t<decltype(obj)>;

Expand All @@ -150,7 +151,7 @@ class RunFn : public Callable {
return "";
};

CStringArray res{};
detail::CStringArray res{};
res.storage.reserve(v.size());
res.ptrs.reserve(v.size() + 1);

Expand Down Expand Up @@ -178,7 +179,7 @@ class RunFn : public Callable {
[[nodiscard]] std::string str() const override { return "<native fn: run>"; }

Object call(std::vector<Object> args) override {
CStringArray argList = RunFn::concatElems(args);
detail::CStringArray argList = RunFn::concatElems(args);
if (m_target == nullptr) {
std::cerr << "Error: Target is not set!\n";
CmdError::getInstance().m_had_error = true;
Expand Down Expand Up @@ -262,4 +263,33 @@ class TargetFn : public Callable {
}
};

class RegisterFn : public Callable {
private:
FnPtr view = [](const std::vector<std::string>& args) -> Object {
auto& target = Context::getTarget();
if (!target && !target->m_started) return "Target is not running!";
if (args.front() == "all")
return target->formatRegisterOutput(&target->getLastKnownThreadState());
auto res = findRegEntry(args.front());
if (!res.hasValue()) return res.error();
return std::format("{}: {}", args.front(),
detail::toHex(readRegValue(
target->getLastKnownThreadState(), *res.value())));
};
SubcommandHandler m_subcmds{{{sv("view"), view}}, "register"};

public:
[[nodiscard]] int arity() const override { return 2; }
[[nodiscard]] std::string str() const override {
return "<native fn: register>";
}
Object call(std::vector<Object> args) override {
std::vector<std::string> convertedArgs = detail::convertToStr(args);
if (convertedArgs.empty()) return std::monostate{};
const std::string subcmd = convertedArgs.front();
convertedArgs.erase(convertedArgs.begin());
return m_subcmds.exec(subcmd, convertedArgs);
}
};

#endif
4 changes: 3 additions & 1 deletion src/cmd/token_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#define CAESAR_TOKEN_TYPE_H

#include <cstdint>
// Needed here separately to not recursively include typedefs.hpp
using u8 = std::uint8_t;

enum class TokenType : std::uint8_t {
enum class TokenType : u8 {
LEFT_PAREN,
RIGHT_PAREN,
MINUS,
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_library(caesar_core STATIC
context.hpp
target.hpp
target.cpp
platform.hpp
)

target_include_directories(caesar_core PUBLIC
Expand Down
1 change: 1 addition & 0 deletions src/core/macho/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_library(caesar_macho STATIC
ports.hpp
mach_exc.h
mach_excServer.h
types.hpp
)

target_include_directories(caesar_macho PUBLIC
Expand Down
41 changes: 27 additions & 14 deletions src/core/macho/macho.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <macho/ports.hpp>
#include <stdexcept>

#include "platform.hpp"
#include "target.hpp"
extern "C" {
#include "mach_exc.h"
Expand Down Expand Up @@ -80,10 +81,11 @@ void Macho::dumpHeader(int offset) {
auto header = loadBytesAndMaybeSwap<mach_header_64>(offset);
ncmds = header.ncmds;
loadCmdsOffset += headerSize;
std::cout << Macho::cpuTypeName(header.cputype) << '\n';
} else {
const int headerSize = sizeof(struct mach_header);
constexpr size_t headerSize = sizeof(struct mach_header);
auto header = loadBytesAndMaybeSwap<mach_header>(offset);
ncmds = header.ncmds;
loadCmdsOffset += headerSize;
}

dumpSegmentCommands(loadCmdsOffset, ncmds);
Expand Down Expand Up @@ -134,7 +136,7 @@ Macho::Macho(std::ifstream f, std::string filePath)
}

// TODO: Add appropriate error messages
i32 Macho::launch(CStringArray& argList) {
i32 Macho::launch(detail::CStringArray& argList) {
pid_t pid = 0;
int status = 0;
posix_spawnattr_t attr = nullptr;
Expand Down Expand Up @@ -349,30 +351,35 @@ kern_return_t catch_mach_exception_raise_state_identity(
#pragma unused(newStateCnt)
auto& target = Context::getTarget();
target->setTargetState(TargetState::STOPPED);

task_suspend(task);

auto* macho = dynamic_cast<Macho*>(Context::getTarget().get());
auto* oldArmState = reinterpret_cast<arm_thread_state64_t*>(oldState);
auto* newArmState = reinterpret_cast<arm_thread_state64_t*>(newState);
memcpy(newArmState, oldArmState, sizeof(arm_thread_state64_t));
auto* oldArmState = reinterpret_cast<ThreadState*>(oldState);
auto* newArmState = reinterpret_cast<ThreadState*>(newState);

memcpy(newArmState, oldArmState, sizeof(ThreadState));

std::cout << Macho::exceptionReason(exc, codeCnt, code);
std::cout << std::format("PC @ {}\n",
toHex(oldArmState->__pc - macho->getAslrSlide()));
std::cout << macho->formatRegisterOutput(oldArmState);

if (macho->getThreadPort() == 0) macho->setThreadPort(thread);

if (exc == EXC_BAD_INSTRUCTION && *flavour == ARM_THREAD_STATE64) {
std::cout << std::format("Fault @ {}!\n",
toHex(oldArmState->__pc - macho->getAslrSlide()));
newArmState->__pc += 4;
std::cout << std::format("Resuming @ {}\n",
toHex(newArmState->__pc - macho->getAslrSlide()));
std::cout << std::format(
"Fault @ {}!\n",
detail::toHex(oldArmState->pc - macho->getAslrSlide()));
newArmState->pc += 4;
std::cout << std::format(
"Resuming @ {}\n",
detail::toHex(newArmState->pc - macho->getAslrSlide()));
}

if (exc == EXC_BREAKPOINT) {
macho->restorePrevIns(oldArmState->__pc - macho->getAslrSlide());
macho->restorePrevIns(oldArmState->pc - macho->getAslrSlide());
}

macho->setThreadState(newArmState);
*newStateCnt = oldStateCnt;
return KERN_SUCCESS;
}
Expand Down Expand Up @@ -618,3 +625,9 @@ i32 Macho::disableBreakpoint(u64 addr, bool remove) {

return 0;
}

void Macho::setThreadState(ThreadState* state) {
memcpy(&m_last_thread_state, state, sizeof(ThreadState));
}

ThreadState& Macho::getLastKnownThreadState() { return m_last_thread_state; }
21 changes: 19 additions & 2 deletions src/core/macho/macho.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach/arm/kern_return.h>
#include <mach/arm/thread_status.h>
#include <mach/exception_types.h>
#include <mach/mach_types.h>
#include <mach/machine.h>
Expand All @@ -13,6 +14,7 @@
#include <fstream>
#include <string>

#include "core/platform.hpp"
#include "core/target.hpp"
#include "core/util.hpp"

Expand All @@ -25,14 +27,28 @@ class Macho final : public Target {
public:
explicit Macho(std::ifstream f, std::string filePath);

#ifdef __arm64__
static constexpr thread_state_flavor_t THREAD_FLAVOUR = ARM_THREAD_STATE64;
static constexpr mach_msg_type_number_t THREAD_STATE_COUNT =
ARM_THREAD_STATE64_COUNT;
#endif

#ifdef __i386__
static constexpr thread_state_flavor_t THREAD_FLAVOUR = X86_THREAD_STATE64;
static constexpr mach_msg_type_number_t THREAD_STATE_COUNT =
X86_THREAD_STATE64_COUNT;
#endif

void dumpHeader(int offset) override;
i32 attach() override;
i32 setBreakpoint(u64 addr) override;
i32 disableBreakpoint(u64 addr, bool remove) override;
i32 launch(CStringArray& argList) override;
i32 launch(detail::CStringArray& argList) override;
void detach() override;
void eventLoop() override;
void resume(ResumeType cond) override;
void setThreadState(ThreadState* state) override;
ThreadState& getLastKnownThreadState() override;

static std::string exceptionReason(exception_type_t exc,
mach_msg_type_number_t codeCnt,
Expand All @@ -50,6 +66,7 @@ class Macho final : public Target {
mach_port_t m_exc_port = 0;
mach_port_t m_thread_port = 0;
bool m_is_swap = false;
ThreadState m_last_thread_state{};
static constexpr std::array<CpuTypeNames, 4> CPU_TYPE_NAMES = {
{{.cpu_type = CPU_TYPE_I386, .cpu_name = "i386"},
{.cpu_type = CPU_TYPE_X86_64, .cpu_name = "x86_64"},
Expand All @@ -66,7 +83,7 @@ class Macho final : public Target {
T buf;
m_file.seekg(offset, std::ios::beg);
m_file.read(std::bit_cast<char*>(&buf), sizeof(T));
if (m_is_swap) SwapDescriptor<T>::swap(&buf);
if (m_is_swap) detail::SwapDescriptor<T>::swap(&buf);
return buf;
}

Expand Down
Loading
Loading