Skip to content

Commit b4903f8

Browse files
committed
feat: switch logging to os_log via oslog crate, use cfg(debug_assertions)
Replace manual eprintln! logger with Apple's unified logging system (os_log) using the oslog crate. Logs now appear in Console.app under subsystem "com.specter", category "memory". Replace cfg(feature = "dev_release") with cfg(debug_assertions) across all modules so debug logging is automatic in debug builds with no feature flag needed. Remove the dev_release feature from Cargo.toml. Bump version to 1.0.5.
1 parent 5c9fc91 commit b4903f8

14 files changed

Lines changed: 89 additions & 61 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "specter-mem"
3-
version = "1.0.4"
3+
version = "1.0.5"
44
edition = "2024"
55
description = "ARM64 memory manipulation framework for iOS/macOS — inline hooking, stealth code patching, hardware breakpoints, and shellcode loading"
66
license = "MIT"
@@ -19,9 +19,6 @@ targets = ["aarch64-apple-darwin"]
1919
name = "specter"
2020
crate-type = ["staticlib", "rlib"]
2121

22-
[features]
23-
dev_release = []
24-
2522
[dependencies]
2623
mach2 = "0.6.0"
2724
once_cell = "1"
@@ -31,3 +28,5 @@ libc = "0.2"
3128
hex = "0.4"
3229
dashmap = "6"
3330
jit-assembler = { version = "0.3", default-features = false, features = ["aarch64"] }
31+
log = "0.4"
32+
oslog = { version = "0.2", default-features = false, features = ["logger"] }

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ make check # verify exported symbols match specter.h
3333
-L<path> -lspectre -lc++ -framework Foundation -framework Security
3434
```
3535

36+
## Debug logging
37+
38+
Debug builds automatically log to Apple's unified logging system (`os_log`). Messages appear in **Console.app** under subsystem `com.specter`, category `memory`.
39+
40+
To filter in Console.app, use:
41+
42+
```
43+
subsystem:com.specter category:memory
44+
```
45+
Logging is compiled out of release builds (`cfg(debug_assertions)`).
46+
3647
## Docs
3748

3849
[docs/usage.md](docs/usage.md) — C/C++ API reference and examples

src/memory/allocation/shellcode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Shellcode loader with symbol resolution and position-independent code support
22
33
use crate::memory::{code_cave, info::symbol, rw};
4-
#[cfg(feature = "dev_release")]
4+
#[cfg(debug_assertions)]
55
use crate::utils::logger;
66
use std::arch::asm;
77
use std::collections::HashMap;
@@ -254,7 +254,7 @@ impl ShellcodeBuilder {
254254
invalidate_icache(cave.address as *mut c_void, code.len());
255255
}
256256

257-
#[cfg(feature = "dev_release")]
257+
#[cfg(debug_assertions)]
258258
logger::info(&format!(
259259
"Shellcode loaded at {:#x} ({} bytes)",
260260
cave.address,

src/memory/info/code_cave.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Code cave finder and manager
22
33
use crate::memory::info::{image, scan};
4-
#[cfg(feature = "dev_release")]
4+
#[cfg(debug_assertions)]
55
use crate::utils::logger;
66
use once_cell::sync::Lazy;
77
use parking_lot::Mutex;
@@ -300,7 +300,7 @@ pub fn find_caves_in_image(
300300

301301
let scan_size = 32 * 1024 * 1024; // 32MB
302302

303-
#[cfg(feature = "dev_release")]
303+
#[cfg(debug_assertions)]
304304
logger::info(&format!(
305305
"Scanning image '{}' at {:#x} for code caves (min size: {} bytes)",
306306
image_name, base, min_size
@@ -337,7 +337,7 @@ pub fn allocate_cave(size: usize) -> Result<CodeCave, CodeCaveError> {
337337
if cave.size >= size {
338338
cave.allocate();
339339
REGISTRY.lock().register(cave.clone())?;
340-
#[cfg(feature = "dev_release")]
340+
#[cfg(debug_assertions)]
341341
logger::info(&format!(
342342
"Allocated code cave at {:#x} (size: {} bytes)",
343343
cave.address, cave.size
@@ -382,7 +382,7 @@ pub fn allocate_cave_near(target: usize, size: usize) -> Result<CodeCave, CodeCa
382382

383383
cave.allocate();
384384
REGISTRY.lock().register(cave.clone())?;
385-
#[cfg(feature = "dev_release")]
385+
#[cfg(debug_assertions)]
386386
logger::info(&format!(
387387
"Allocated code cave near {:#x} at {:#x} (size: {} bytes)",
388388
target, cave.address, cave.size
@@ -404,7 +404,7 @@ pub fn allocate_cave_near(target: usize, size: usize) -> Result<CodeCave, CodeCa
404404
pub fn free_cave(address: usize) -> Result<(), CodeCaveError> {
405405
let mut cave = REGISTRY.lock().unregister(address)?;
406406
cave.free();
407-
#[cfg(feature = "dev_release")]
407+
#[cfg(debug_assertions)]
408408
logger::info(&format!("Freed code cave at {:#x}", address));
409409
Ok(())
410410
}
@@ -461,6 +461,6 @@ pub fn get_cave_stats() -> (usize, usize) {
461461
/// Clears all allocated code caves from the registry
462462
pub fn clear_all_caves() {
463463
REGISTRY.lock().clear();
464-
#[cfg(feature = "dev_release")]
464+
#[cfg(debug_assertions)]
465465
logger::info("Cleared all allocated code caves");
466466
}

src/memory/info/image.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! Dynamic library image lookup utilities
22
3-
#[cfg(feature = "dev_release")]
3+
#[cfg(debug_assertions)]
44
use crate::utils::logger;
55
use std::ffi::CStr;
66

7-
#[cfg(feature = "dev_release")]
7+
#[cfg(debug_assertions)]
88
use mach2::dyld::_dyld_get_image_vmaddr_slide;
99
use mach2::dyld::{_dyld_get_image_header, _dyld_get_image_name, _dyld_image_count};
1010
use thiserror::Error;
@@ -51,10 +51,10 @@ pub fn get_image_base(image_name: &str) -> Result<usize, ImageError> {
5151
let name = CStr::from_ptr(name_ptr).to_string_lossy();
5252
if name.contains(image_name) {
5353
let header = _dyld_get_image_header(i);
54-
#[cfg(feature = "dev_release")]
54+
#[cfg(debug_assertions)]
5555
let slide = _dyld_get_image_vmaddr_slide(i);
5656

57-
#[cfg(feature = "dev_release")]
57+
#[cfg(debug_assertions)]
5858
logger::info(&format!(
5959
"Found image: {} (Index: {}, Base: {:p}, Slide: {:#x})",
6060
name, i, header, slide
@@ -69,7 +69,7 @@ pub fn get_image_base(image_name: &str) -> Result<usize, ImageError> {
6969
}
7070
}
7171

72-
#[cfg(feature = "dev_release")]
72+
#[cfg(debug_assertions)]
7373
logger::warning(&format!("Image not found: {}", image_name));
7474
Err(ImageError::NotFound(image_name.to_string()))
7575
}

src/memory/info/protection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Memory page protection query utilities
22
3-
#[cfg(feature = "dev_release")]
3+
#[cfg(debug_assertions)]
44
use crate::utils::logger;
55
use mach2::{
66
kern_return::KERN_SUCCESS,
@@ -252,7 +252,7 @@ pub fn get_all_regions() -> Result<Vec<RegionInfo>, ProtectionError> {
252252
address += size;
253253
}
254254

255-
#[cfg(feature = "dev_release")]
255+
#[cfg(debug_assertions)]
256256
logger::info(&format!("Found {} memory regions", regions.len()));
257257
Ok(regions)
258258
}

src/memory/info/scan.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::memory::image;
44
use crate::memory::info::protection;
5-
#[cfg(feature = "dev_release")]
5+
#[cfg(debug_assertions)]
66
use crate::utils::logger;
77
use once_cell::sync::Lazy;
88
use parking_lot::Mutex;
@@ -165,7 +165,7 @@ pub fn scan_pattern_cached(
165165
{
166166
let cache = SCAN_CACHE.lock();
167167
if let Some(cached) = cache.get(&cache_key) {
168-
#[cfg(feature = "dev_release")]
168+
#[cfg(debug_assertions)]
169169
logger::info(&format!("Cache hit for pattern: {}", ida_pattern));
170170
return Ok(cached.clone());
171171
}
@@ -180,7 +180,7 @@ pub fn scan_pattern_cached(
180180
/// Clears the scan cache
181181
pub fn clear_cache() {
182182
SCAN_CACHE.lock().clear();
183-
#[cfg(feature = "dev_release")]
183+
#[cfg(debug_assertions)]
184184
logger::info("Scan cache cleared");
185185
}
186186

src/memory/manipulation/backup.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use crate::memory::manipulation::patch::stealth_write;
77
use crate::memory::platform::thread;
8-
#[cfg(feature = "dev_release")]
8+
#[cfg(debug_assertions)]
99
use crate::utils::logger;
1010
use thiserror::Error;
1111

@@ -29,7 +29,7 @@ impl MemoryBackup {
2929
pub fn create(address: usize, size: usize) -> Result<Self, BackupError> {
3030
let original_bytes = unsafe { read_bytes(address, size) };
3131

32-
#[cfg(feature = "dev_release")]
32+
#[cfg(debug_assertions)]
3333
logger::debug(&format!("Backup created: {:#x} ({} bytes)", address, size));
3434

3535
Ok(Self {
@@ -50,7 +50,7 @@ impl MemoryBackup {
5050

5151
thread::resume_threads(&suspended);
5252

53-
#[cfg(feature = "dev_release")]
53+
#[cfg(debug_assertions)]
5454
logger::debug(&format!("Backup restored: {:#x}", self.address));
5555
}
5656
Ok(())

src/memory/manipulation/checksum.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
1111
use std::thread::{self, JoinHandle};
1212
use std::time::Duration;
1313

14-
#[cfg(feature = "dev_release")]
14+
#[cfg(debug_assertions)]
1515
use crate::utils::logger;
1616

1717
use crate::memory::info::protection;
@@ -284,7 +284,7 @@ fn default_tamper_callback(tampered: &[usize]) {
284284
use super::hook::restore_hook_bytes;
285285

286286
for &addr in tampered {
287-
#[cfg(feature = "dev_release")]
287+
#[cfg(debug_assertions)]
288288
logger::warning(&format!("Hook tampered at {:#x}, restoring...", addr));
289289

290290
if restore_hook_bytes(addr) {
@@ -295,10 +295,10 @@ fn default_tamper_callback(tampered: &[usize]) {
295295
}
296296
drop(checksums);
297297

298-
#[cfg(feature = "dev_release")]
298+
#[cfg(debug_assertions)]
299299
logger::info(&format!("Hook restored at {:#x}", addr));
300300
} else {
301-
#[cfg(feature = "dev_release")]
301+
#[cfg(debug_assertions)]
302302
logger::error(&format!("Failed to restore hook at {:#x}", addr));
303303
}
304304
}
@@ -327,7 +327,7 @@ pub fn start_monitor(
327327
let state_clone = Arc::clone(&state);
328328

329329
let handle = thread::spawn(move || {
330-
#[cfg(feature = "dev_release")]
330+
#[cfg(debug_assertions)]
331331
logger::info("Integrity monitor started");
332332

333333
while state_clone.running.load(Ordering::Relaxed) {
@@ -343,7 +343,7 @@ pub fn start_monitor(
343343
}
344344
}
345345

346-
#[cfg(feature = "dev_release")]
346+
#[cfg(debug_assertions)]
347347
logger::info("Integrity monitor stopped");
348348
});
349349

src/memory/manipulation/hook.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use super::checksum;
1414
use crate::config;
1515
use crate::memory::info::{code_cave, symbol};
1616
use crate::memory::{image, patch, protection, thread};
17-
#[cfg(feature = "dev_release")]
17+
#[cfg(debug_assertions)]
1818
use crate::utils::logger;
1919
use once_cell::sync::Lazy;
2020
use parking_lot::Mutex;
@@ -202,14 +202,14 @@ pub unsafe fn hook_symbol(symbol_name: &str, replacement: usize) -> Result<Hook,
202202
pub unsafe fn install_at_address(target: usize, replacement: usize) -> Result<usize, HookError> {
203203
unsafe {
204204
if REGISTRY.lock().contains_key(&target) {
205-
#[cfg(feature = "dev_release")]
205+
#[cfg(debug_assertions)]
206206
logger::warning("Hook already exists at target");
207207
return Err(HookError::AlreadyExists(target));
208208
}
209209

210210
let first_instr = super::rw::read::<u32>(target).map_err(|_| HookError::PatchFailed)?;
211211
if is_b_instruction(first_instr) {
212-
#[cfg(feature = "dev_release")]
212+
#[cfg(debug_assertions)]
213213
logger::debug("Detected thunk, using short hook");
214214
return install_thunk_hook(target, replacement, first_instr);
215215
}
@@ -448,7 +448,7 @@ unsafe fn install_thunk_hook(
448448
);
449449
let _ = checksum::register(target, 4);
450450
thread::resume_threads(&suspended);
451-
#[cfg(feature = "dev_release")]
451+
#[cfg(debug_assertions)]
452452
logger::debug("Thunk hook installed");
453453
Ok(trampoline_base)
454454
}
@@ -517,7 +517,7 @@ unsafe fn install_regular_hook(target: usize, replacement: usize) -> Result<usiz
517517
);
518518
let _ = checksum::register(target, MAX_STOLEN_BYTES);
519519
thread::resume_threads(&suspended);
520-
#[cfg(feature = "dev_release")]
520+
#[cfg(debug_assertions)]
521521
logger::debug("Hook installed");
522522
Ok(trampoline_base)
523523
}
@@ -567,7 +567,7 @@ pub unsafe fn remove_at_address(target: usize) -> bool {
567567
libc::munmap(entry.trampoline as *mut c_void, TRAMPOLINE_SIZE);
568568
checksum::unregister(target);
569569
thread::resume_threads(&suspended);
570-
#[cfg(feature = "dev_release")]
570+
#[cfg(debug_assertions)]
571571
logger::debug("Hook removed");
572572
true
573573
}
@@ -892,7 +892,7 @@ pub unsafe fn install_in_cave_at_address(
892892
let _ = checksum::register(target, MAX_STOLEN_BYTES);
893893

894894
thread::resume_threads(&suspended);
895-
#[cfg(feature = "dev_release")]
895+
#[cfg(debug_assertions)]
896896
logger::debug("Cave hook installed");
897897

898898
Ok(Hook {

0 commit comments

Comments
 (0)