1
Fork 0

Compare commits

..

10 commits

Author SHA1 Message Date
Jan-Erik Rediger c27e748f43 chore: Release oelf version 0.0.4
Some checks failed
CI / linux (aarch64) (push) Failing after 1s
CI / linux (x86) (push) Failing after 1s
CI / linux (x86_64) (push) Failing after 1s
CI / sdist (push) Failing after 1s
CI / windows (x64) (push) Has been cancelled
CI / windows (x86) (push) Has been cancelled
CI / macos (aarch64) (push) Has been cancelled
CI / macos (x86_64) (push) Has been cancelled
CI / Release (push) Has been cancelled
2023-12-11 16:46:41 +01:00
Jan-Erik Rediger f76597cb7c Expose load commands 2023-12-11 16:44:00 +01:00
Jan-Erik Rediger 6dba4064f2 Expose segments 2023-12-11 16:43:55 +01:00
Jan-Erik Rediger 53708c2bb4 Iterate through a fat mach-o file to find the first single-arch mach-o part 2023-12-11 15:54:30 +01:00
Jan-Erik Rediger 1297ce2668 Oops, forgot to commit this 2023-12-11 15:54:13 +01:00
Jan-Erik Rediger ffec4b5769 Release v0.0.3
Some checks failed
CI / linux (aarch64) (push) Failing after 1s
CI / linux (x86) (push) Failing after 1s
CI / linux (x86_64) (push) Failing after 1s
CI / sdist (push) Failing after 1s
CI / windows (x64) (push) Has been cancelled
CI / windows (x86) (push) Has been cancelled
CI / macos (aarch64) (push) Has been cancelled
CI / macos (x86_64) (push) Has been cancelled
CI / Release (push) Has been cancelled
2023-12-09 19:37:53 +01:00
Jan-Erik Rediger c07018a3aa CI: Run the test script on macOS 2023-12-09 19:36:32 +01:00
Jan-Erik Rediger 992506e516 Change test script to just print 2023-12-09 19:36:32 +01:00
Jan-Erik Rediger 25d945f084 Generate CI config 2023-12-09 19:12:42 +01:00
Jan-Erik Rediger e005020934 Renamed 2023-12-09 19:12:01 +01:00
9 changed files with 282 additions and 40 deletions

121
.github/workflows/CI.yml vendored Normal file
View file

@ -0,0 +1,121 @@
name: CI
on:
push:
branches:
- main
tags:
- '*'
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, x86, aarch64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
- name: Test
run: |
python3 -m venv ${PWD}/.venv
. .venv/bin/activate
maturin --version
maturin develop
python3 test.py
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing *

16
Cargo.lock generated
View file

@ -31,14 +31,6 @@ dependencies = [
"scroll", "scroll",
] ]
[[package]]
name = "goblin-pyo3"
version = "0.0.2"
dependencies = [
"goblin",
"pyo3",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -82,6 +74,14 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "oelf"
version = "0.0.4"
dependencies = [
"goblin",
"pyo3",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.19.0" version = "1.19.0"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "goblin-pyo3" name = "oelf"
version = "0.0.2" version = "0.0.4"
edition = "2021" edition = "2021"
[lib] [lib]

View file

@ -10,4 +10,4 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
] ]
dynamic = ["version"]

View file

@ -3,19 +3,23 @@ use std::{
io::Read, io::Read,
}; };
use goblin::mach::Mach; use goblin::mach::{Mach, SingleArch};
use pyo3::{exceptions::PyTypeError, prelude::*}; use pyo3::{exceptions::PyTypeError, prelude::*};
mod exports; mod exports;
mod header; mod header;
mod imports; mod imports;
mod load_commands;
mod sections; mod sections;
mod segments;
mod symbols; mod symbols;
use exports::Export; use exports::Export;
use header::Header; use header::Header;
use imports::Import; use imports::Import;
use load_commands::LoadCommand;
use sections::{Section, Sections}; use sections::{Section, Sections};
use segments::Segment;
use symbols::Symbols; use symbols::Symbols;
#[pyclass] #[pyclass]
@ -57,6 +61,24 @@ impl Object {
let macho = match object { let macho = match object {
goblin::Object::Mach(Mach::Binary(macho)) => macho, goblin::Object::Mach(Mach::Binary(macho)) => macho,
goblin::Object::Mach(Mach::Fat(march)) => {
let mut macho = None;
for arch in &march {
let arch = arch.map_err(|_| {
PyErr::new::<PyTypeError, _>("cannot parse single arch from Mach-O file")
})?;
if let SingleArch::MachO(m) = arch {
macho = Some(m);
break;
}
}
match macho {
Some(macho) => macho,
None => return Err(PyErr::new::<PyTypeError, _>("not a macho file")),
}
}
_ => return Err(PyErr::new::<PyTypeError, _>("not a macho file")), _ => return Err(PyErr::new::<PyTypeError, _>("not a macho file")),
}; };
@ -82,13 +104,23 @@ impl Object {
Symbols::from(self.macho().symbols()) Symbols::from(self.macho().symbols())
} }
fn segments(&self) -> Vec<Segment> {
self.macho()
.segments
.into_iter()
.map(|seg| seg.into())
.collect()
}
fn sections(&self) -> Sections { fn sections(&self) -> Sections {
let macho = self.macho(); let macho = self.macho();
let mut sections = vec![]; let mut sections = vec![];
let mut idx = 0;
for sect_iter in macho.segments.sections() { for sect_iter in macho.segments.sections() {
sections.extend(sect_iter.map(|section| { sections.extend(sect_iter.map(|section| {
idx += 1;
let (sect, _data) = section.unwrap(); let (sect, _data) = section.unwrap();
Section::from(sect) Section::from((idx, sect))
})); }));
} }
Sections { sections } Sections { sections }
@ -119,6 +151,14 @@ impl Object {
.map_err(|_| PyErr::new::<PyTypeError, _>("failed"))?; .map_err(|_| PyErr::new::<PyTypeError, _>("failed"))?;
Ok(imports.into_iter().map(|exp| exp.into()).collect()) Ok(imports.into_iter().map(|exp| exp.into()).collect())
} }
fn load_commands(&self) -> Vec<LoadCommand> {
self.macho()
.load_commands
.iter()
.map(|cmd| cmd.into())
.collect()
}
} }
impl Drop for Object { impl Drop for Object {

26
src/load_commands.rs Normal file
View file

@ -0,0 +1,26 @@
use pyo3::prelude::*;
#[derive(Debug, Clone)]
#[pyclass]
pub struct LoadCommand {
#[pyo3(get)]
offset: usize,
#[pyo3(get)]
command: String,
}
#[pymethods]
impl LoadCommand {
fn __repr__(&self) -> String {
format!("{:?}", self)
}
}
impl From<&goblin::mach::load_command::LoadCommand> for LoadCommand {
fn from(lcmd: &goblin::mach::load_command::LoadCommand) -> Self {
LoadCommand {
offset: lcmd.offset,
command: format!("{:?}", lcmd.command),
}
}
}

View file

@ -3,6 +3,8 @@ use pyo3::prelude::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[pyclass] #[pyclass]
pub struct Section { pub struct Section {
#[pyo3(get)]
index: usize,
#[pyo3(get)] #[pyo3(get)]
name: Option<String>, name: Option<String>,
#[pyo3(get)] #[pyo3(get)]
@ -30,9 +32,10 @@ impl Section {
} }
} }
impl From<goblin::mach::segment::Section> for Section { impl From<(usize, goblin::mach::segment::Section)> for Section {
fn from(section: goblin::mach::segment::Section) -> Self { fn from((index, section): (usize, goblin::mach::segment::Section)) -> Self {
Section { Section {
index,
name: section.name().ok().map(|s| s.to_string()), name: section.name().ok().map(|s| s.to_string()),
segment: section.segname().ok().map(|s| s.to_string()), segment: section.segname().ok().map(|s| s.to_string()),
addr: section.addr, addr: section.addr,

54
src/segments.rs Normal file
View file

@ -0,0 +1,54 @@
use pyo3::prelude::*;
#[derive(Debug, Clone)]
#[pyclass]
pub struct Segment {
#[pyo3(get)]
cmd: u32,
#[pyo3(get)]
cmdsize: u32,
#[pyo3(get)]
name: Option<String>,
#[pyo3(get)]
vmaddr: u64,
#[pyo3(get)]
vmsize: u64,
#[pyo3(get)]
fileoff: u64,
#[pyo3(get)]
filesize: u64,
#[pyo3(get)]
maxprot: u32,
#[pyo3(get)]
initprot: u32,
#[pyo3(get)]
nsects: u32,
#[pyo3(get)]
flags: u32,
}
#[pymethods]
impl Segment {
fn __repr__(&self) -> String {
format!("{:?}", self)
}
}
impl From<&goblin::mach::segment::Segment<'_>> for Segment {
fn from(segm: &goblin::mach::segment::Segment) -> Self {
let segname = segm.name().ok().map(|s| s.to_string());
Segment {
cmd: segm.cmd,
cmdsize: segm.cmdsize,
name: segname,
vmaddr: segm.vmaddr,
vmsize: segm.vmsize,
fileoff: segm.fileoff,
filesize: segm.filesize,
maxprot: segm.maxprot,
initprot: segm.initprot,
nsects: segm.nsects,
flags: segm.flags,
}
}
}

48
test.py
View file

@ -1,28 +1,17 @@
import sys
import oelf import oelf
g = oelf.Object("test.py") path = "target/debug/liboelf.dylib"
g = oelf.Object("mylib.dylib") if len(sys.argv) > 1:
print(g.header) path = sys.argv[1]
print(g.name)
g = oelf.Object(path)
print(f"{g.header=}")
print(f"{g.name=}")
print("symbols") print("symbols")
globsym = None for symbol in g.symbols():
start = -1 print(symbol)
end = 9999999999
for sym in g.symbols():
if "META" in sym.name and sym.is_global:
if globsym is None:
globsym = sym
start = sym.meta.n_value
for sym in g.symbols():
if sym.meta.n_value > start and sym.meta.n_value < end:
print(f"sym after the found one: {sym}")
end = sym.meta.n_value
print(f"found symbol {globsym.name} from {start} to {end}, size: {end-start}")
print(globsym)
print("libs") print("libs")
print(g.libs) print(g.libs)
@ -31,12 +20,21 @@ print("rpaths")
print(g.rpaths) print(g.rpaths)
print("exports") print("exports")
print(len(g.exports())) for export in g.exports():
print(export)
print("imports") print("imports")
print(len(g.imports())) for imp in g.imports():
print(imp)
print("segments")
for segment in g.segments():
print(f"{segment}")
print("sections") print("sections")
for section in g.sections():
print(f"{section}")
for idx, section in enumerate(g.sections()): print("load commands")
print(f"{idx+1}. {section}") for lcmd in g.load_commands():
print(lcmd)