mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 17:12:49 -10:00
Compare commits
91
Commits
No files matched your search
+51
-25
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ${{ fromJSON(matrix.runner) }}
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install MinGW toolchain for cgo support
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
msystem: ${{ matrix.sys }}
|
||||
install: ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -95,14 +95,40 @@ jobs:
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
- name: Upload GObject introspection data
|
||||
if: ${{ matrix.id == 'linux-x86_64' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-gir
|
||||
path: build/src/api/Frida-1.0.gir
|
||||
- name: Test
|
||||
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
|
||||
run: make test
|
||||
|
||||
publish-gir:
|
||||
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.native.result == 'success' }}
|
||||
needs:
|
||||
- native
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download GObject introspection data
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-gir
|
||||
path: gir
|
||||
- name: Attach to GitHub release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
gir/Frida-1.0.gir
|
||||
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
cross:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -117,7 +143,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install toolchain
|
||||
@@ -125,7 +151,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -138,7 +164,7 @@ jobs:
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
@@ -152,11 +178,11 @@ jobs:
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -168,7 +194,7 @@ jobs:
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-linux-${{ matrix.arch }}
|
||||
path: build/src/devkit/
|
||||
@@ -185,7 +211,7 @@ jobs:
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -316,11 +342,11 @@ jobs:
|
||||
runs-on: ${{ startsWith(matrix.id, 'ios-') && 'macos-latest' || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -347,7 +373,7 @@ jobs:
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
@@ -406,7 +432,7 @@ jobs:
|
||||
container: ghcr.io/frida/qnx-tools:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -415,7 +441,7 @@ jobs:
|
||||
./configure --host=arm-unknown-nto-qnx6.5.0eabi ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-qnx-armeabi
|
||||
path: build/src/devkit/
|
||||
@@ -441,7 +467,7 @@ jobs:
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -475,7 +501,7 @@ jobs:
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -504,24 +530,24 @@ jobs:
|
||||
- mobile
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Download macOS/x86_64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-macos-x86_64
|
||||
path: devkits/macos-x86_64
|
||||
- name: Download macOS/arm64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-macos-arm64
|
||||
path: devkits/macos-arm64
|
||||
- name: Download iOS/arm64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-ios-arm64
|
||||
path: devkits/ios-arm64
|
||||
- name: Download iOS/arm64-simulator devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-ios-arm64-simulator
|
||||
path: devkits/ios-arm64-simulator
|
||||
@@ -584,7 +610,7 @@ jobs:
|
||||
-headers xcframework_headers/ios-arm64-simulator \
|
||||
-output FridaCore.xcframework
|
||||
- name: Upload FridaCore.xcframework
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: FridaCore.xcframework
|
||||
path: FridaCore.xcframework
|
||||
@@ -596,7 +622,7 @@ jobs:
|
||||
- xcframework
|
||||
steps:
|
||||
- name: Download FridaCore.xcframework artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: FridaCore.xcframework
|
||||
path: dist
|
||||
@@ -619,7 +645,7 @@ jobs:
|
||||
cd dist
|
||||
zip -r FridaCore.xcframework.zip FridaCore.xcframework
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
dist/FridaCore.xcframework.zip
|
||||
|
||||
+5
-1
@@ -624,7 +624,11 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
return is_for_us or is_for_child
|
||||
|
||||
if coredata.CoreData.is_per_machine_option(k):
|
||||
return k.machine is coredata.MachineChoice.BUILD
|
||||
# The compat build generates its own machine files, so forwarding the
|
||||
# parent's build-machine options would inject a foreign toolchain. In a
|
||||
# native compat build that conjures a compiler-less build machine and
|
||||
# breaks any native: true target.
|
||||
return False
|
||||
|
||||
if k.is_builtin():
|
||||
if k.name in {"buildtype", "genvslite"}:
|
||||
|
||||
@@ -292,6 +292,8 @@ namespace Frida.Agent {
|
||||
|
||||
start.begin ((owned) padder);
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
main_loop.run ();
|
||||
|
||||
main_context.pop_thread_default ();
|
||||
@@ -362,6 +364,16 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
@@ -924,6 +936,8 @@ namespace Frida.Agent {
|
||||
private void on_session_closed (BaseAgentSession base_session) {
|
||||
LiveAgentSession session = (LiveAgentSession) base_session;
|
||||
|
||||
GLib.info ("detach-trace: Agent emitting provider closed id=%s", session.id.handle);
|
||||
|
||||
closed (session.id);
|
||||
|
||||
unregister_session (session);
|
||||
@@ -1724,4 +1738,8 @@ namespace Frida.Agent {
|
||||
return repeat;
|
||||
});
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
+32
-10
@@ -139,37 +139,35 @@ namespace Frida {
|
||||
}
|
||||
|
||||
internal Libbpf.Object handle;
|
||||
private char[] kernel_log_buf;
|
||||
|
||||
public static BpfObject open (string name, uint8[] blob) throws Error {
|
||||
var log_buf = new char[128 * 1024];
|
||||
|
||||
var opts = Libbpf.Object.OpenOpts ();
|
||||
opts.sz = sizeof (Libbpf.Object.OpenOpts);
|
||||
opts.object_name = name;
|
||||
opts.kernel_log_buf = log_buf;
|
||||
|
||||
var log = new LibbpfLogScope ();
|
||||
var handle = Libbpf.Object.open_mem (blob, opts);
|
||||
if (handle == null)
|
||||
throw_libbpf_error (errno);
|
||||
throw_libbpf_error (errno, log.take ());
|
||||
|
||||
return new BpfObject ((owned) handle, (owned) log_buf);
|
||||
return new BpfObject ((owned) handle);
|
||||
}
|
||||
|
||||
private BpfObject (owned Libbpf.Object handle, owned char[] kernel_log_buf) {
|
||||
private BpfObject (owned Libbpf.Object handle) {
|
||||
this.handle = (owned) handle;
|
||||
this.kernel_log_buf = (owned) kernel_log_buf;
|
||||
|
||||
_programs = new Programs (this);
|
||||
_maps = new Maps (this);
|
||||
}
|
||||
|
||||
public void prepare () throws Error {
|
||||
check_libbpf_result (handle.prepare ());
|
||||
var log = new LibbpfLogScope ();
|
||||
check_libbpf_result (handle.prepare (), log.take ());
|
||||
}
|
||||
|
||||
public void load () throws Error {
|
||||
check_libbpf_result (handle.load (), (string) kernel_log_buf);
|
||||
var log = new LibbpfLogScope ();
|
||||
check_libbpf_result (handle.load (), log.take ());
|
||||
}
|
||||
|
||||
public sealed class Programs {
|
||||
@@ -397,6 +395,30 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LibbpfLogScope {
|
||||
private static StringBuilder buffer;
|
||||
|
||||
public LibbpfLogScope () {
|
||||
if (buffer == null)
|
||||
buffer = new StringBuilder ();
|
||||
buffer.truncate ();
|
||||
Libbpf.set_print (on_message);
|
||||
}
|
||||
|
||||
~LibbpfLogScope () {
|
||||
Libbpf.set_print (null);
|
||||
}
|
||||
|
||||
public string take () {
|
||||
return buffer.str.chomp ();
|
||||
}
|
||||
|
||||
private static int on_message (Libbpf.PrintLevel level, string format, va_list args) {
|
||||
buffer.append_vprintf (format, args);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int check_libbpf_result (int result, string details = "") throws Error {
|
||||
if (result < 0)
|
||||
throw_libbpf_error (result, details);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Issues RPC calls over a {@link RpcPeer}, matching responses to requests.
|
||||
*/
|
||||
public sealed class RpcClient : Object {
|
||||
/**
|
||||
* The peer used to send and receive RPC messages.
|
||||
*/
|
||||
public weak RpcPeer peer {
|
||||
get;
|
||||
construct;
|
||||
@@ -7,10 +13,23 @@ namespace Frida {
|
||||
|
||||
private Gee.HashMap<string, PendingResponse> pending_responses = new Gee.HashMap<string, PendingResponse> ();
|
||||
|
||||
/**
|
||||
* Creates an RPC client that talks over @peer.
|
||||
*
|
||||
* @param peer the peer to send and receive messages through
|
||||
*/
|
||||
public RpcClient (RpcPeer peer) {
|
||||
Object (peer: peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a remote method and waits for its result.
|
||||
*
|
||||
* @param method the method name to invoke
|
||||
* @param args the JSON arguments
|
||||
* @param data binary data to accompany the call, if any
|
||||
* @return the method's JSON result
|
||||
*/
|
||||
public async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
string request_id = Uuid.string_random ();
|
||||
|
||||
@@ -69,6 +88,13 @@ namespace Frida {
|
||||
return pending.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds an incoming message to the client, matching it against pending
|
||||
* calls if it is an RPC response.
|
||||
*
|
||||
* @param json the incoming message as a JSON string
|
||||
* @return true if the message was an RPC message and was handled
|
||||
*/
|
||||
public bool try_handle_message (string json) {
|
||||
if (json.index_of ("\"frida:rpc\"") == -1)
|
||||
return false;
|
||||
@@ -157,7 +183,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A transport over which an {@link RpcClient} sends and receives RPC
|
||||
* messages.
|
||||
*/
|
||||
public interface RpcPeer : Object {
|
||||
/**
|
||||
* Posts an RPC message to the peer.
|
||||
*
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data to accompany the message, if any
|
||||
*/
|
||||
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
}
|
||||
+495
-1
@@ -12,6 +12,8 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -31,6 +33,7 @@ namespace Frida {
|
||||
|
||||
public abstract async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void spawn_gating_disabled (SpawnGatingDisabledReason reason);
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void child_added (HostChildInfo info);
|
||||
@@ -78,6 +81,8 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async void enable_debugger (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -356,6 +361,8 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed, nice_agent=%p",
|
||||
this, nice_agent);
|
||||
if (nice_agent != null)
|
||||
close_nice_resources_and_emit_closed.begin ();
|
||||
else
|
||||
@@ -593,6 +600,7 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed", this);
|
||||
closed ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
@@ -755,10 +763,25 @@ namespace Frida {
|
||||
public abstract async void break_and_detach (Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the Gadget should do when a configured breakpoint is hit.
|
||||
*/
|
||||
public enum GadgetBreakpointAction {
|
||||
/**
|
||||
* Invoke the function and return immediately.
|
||||
*/
|
||||
INVOKE_RETURN,
|
||||
/**
|
||||
* Resume execution.
|
||||
*/
|
||||
RESUME,
|
||||
/**
|
||||
* Detach the Gadget.
|
||||
*/
|
||||
DETACH,
|
||||
/**
|
||||
* Apply the page plan.
|
||||
*/
|
||||
PAGE_PLAN,
|
||||
}
|
||||
|
||||
@@ -802,16 +825,37 @@ namespace Frida {
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AuthenticationService17")]
|
||||
/**
|
||||
* Authenticates clients connecting to a {@link PortalService} or
|
||||
* {@link ControlService}.
|
||||
*/
|
||||
public interface AuthenticationService : Object {
|
||||
/**
|
||||
* Authenticates a client by its token.
|
||||
*
|
||||
* @param token the token presented by the client
|
||||
* @return a JSON string describing the authenticated session
|
||||
*/
|
||||
public abstract async string authenticate (string token, Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationService} that accepts a single fixed token.
|
||||
*/
|
||||
public sealed class StaticAuthenticationService : Object, AuthenticationService {
|
||||
/**
|
||||
* The SHA-256 hash of the accepted token.
|
||||
*/
|
||||
public string token_hash {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a service that accepts the given token.
|
||||
*
|
||||
* @param token the token clients must present
|
||||
*/
|
||||
public StaticAuthenticationService (string token) {
|
||||
Object (token_hash: Checksum.compute_for_string (SHA256, token));
|
||||
}
|
||||
@@ -865,6 +909,11 @@ namespace Frida {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
@@ -944,8 +993,57 @@ namespace Frida {
|
||||
throw new Error.PERMISSION_DENIED ("Not authorized, authentication required");
|
||||
}
|
||||
|
||||
// Fires `expired` when a gated process goes unresumed too long, so the backend can cancel
|
||||
// gating instead of letting a stalled client wedge process creation.
|
||||
public sealed class SpawnGatingWatchdog : Object {
|
||||
public signal void expired ();
|
||||
|
||||
public uint timeout_msec {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Gee.Map<uint, Source> deadlines = new Gee.HashMap<uint, Source> ();
|
||||
|
||||
// Default deadline: under the OS's own spawn-start timeouts (Android's is 10s).
|
||||
public SpawnGatingWatchdog (uint timeout_msec = 8000) {
|
||||
Object (timeout_msec: timeout_msec);
|
||||
}
|
||||
|
||||
public void arm (uint pid) {
|
||||
var deadline = new TimeoutSource (timeout_msec);
|
||||
deadline.set_callback (() => {
|
||||
expired ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
deadline.attach (MainContext.get_thread_default ());
|
||||
deadlines[pid] = deadline;
|
||||
}
|
||||
|
||||
public void cancel (uint pid) {
|
||||
Source? deadline;
|
||||
if (deadlines.unset (pid, out deadline))
|
||||
deadline.destroy ();
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
foreach (var deadline in deadlines.values)
|
||||
deadline.destroy ();
|
||||
deadlines.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The realm an agent should run in.
|
||||
*/
|
||||
public enum Realm {
|
||||
/**
|
||||
* The process's native realm.
|
||||
*/
|
||||
NATIVE,
|
||||
/**
|
||||
* An emulated realm, such as code running under binary translation.
|
||||
*/
|
||||
EMULATED;
|
||||
|
||||
public static Realm from_nick (string nick) throws Error {
|
||||
@@ -957,9 +1055,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How the agent should take over exception handling in the target.
|
||||
*/
|
||||
public enum Exceptor {
|
||||
/**
|
||||
* Install handlers and keep the target from overriding them.
|
||||
*/
|
||||
FULL,
|
||||
/**
|
||||
* Install handlers but let the target replace them.
|
||||
*/
|
||||
HANDLER_ONLY,
|
||||
/**
|
||||
* Do not handle exceptions.
|
||||
*/
|
||||
OFF;
|
||||
|
||||
public static Exceptor from_nick (string nick) throws Error {
|
||||
@@ -1045,11 +1155,52 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
public enum SessionDetachReason {
|
||||
/**
|
||||
* Why spawn gating was disabled on a {@link Device}.
|
||||
*/
|
||||
public enum SpawnGatingDisabledReason {
|
||||
/**
|
||||
* A client asked to disable spawn gating.
|
||||
*/
|
||||
APPLICATION_REQUESTED = 1,
|
||||
/**
|
||||
* A caught process went unresumed long enough that the gater cancelled gating to
|
||||
* avoid stalling process creation.
|
||||
*/
|
||||
WATCHDOG_TIMEOUT;
|
||||
|
||||
public static SpawnGatingDisabledReason from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<SpawnGatingDisabledReason> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<SpawnGatingDisabledReason> (this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a {@link Session} was detached from its target.
|
||||
*/
|
||||
public enum SessionDetachReason {
|
||||
/**
|
||||
* The host asked to detach.
|
||||
*/
|
||||
APPLICATION_REQUESTED = 1,
|
||||
/**
|
||||
* The target process was replaced, for example by an exec.
|
||||
*/
|
||||
PROCESS_REPLACED,
|
||||
/**
|
||||
* The target process exited.
|
||||
*/
|
||||
PROCESS_TERMINATED,
|
||||
/**
|
||||
* The connection to the target was lost.
|
||||
*/
|
||||
CONNECTION_TERMINATED,
|
||||
/**
|
||||
* The device became unreachable.
|
||||
*/
|
||||
DEVICE_LOST;
|
||||
|
||||
public static SessionDetachReason from_nick (string nick) throws Error {
|
||||
@@ -1062,19 +1213,61 @@ namespace Frida {
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.Error")]
|
||||
/**
|
||||
* The errors that Frida operations can fail with.
|
||||
*/
|
||||
public errordomain Error {
|
||||
/**
|
||||
* The frida-server is not running.
|
||||
*/
|
||||
SERVER_NOT_RUNNING,
|
||||
/**
|
||||
* The specified executable was not found.
|
||||
*/
|
||||
EXECUTABLE_NOT_FOUND,
|
||||
/**
|
||||
* The specified executable is not supported.
|
||||
*/
|
||||
EXECUTABLE_NOT_SUPPORTED,
|
||||
/**
|
||||
* No process matched the request.
|
||||
*/
|
||||
PROCESS_NOT_FOUND,
|
||||
/**
|
||||
* The process is not responding.
|
||||
*/
|
||||
PROCESS_NOT_RESPONDING,
|
||||
/**
|
||||
* An argument was invalid.
|
||||
*/
|
||||
INVALID_ARGUMENT,
|
||||
/**
|
||||
* The operation is not valid in the current state.
|
||||
*/
|
||||
INVALID_OPERATION,
|
||||
/**
|
||||
* Permission was denied.
|
||||
*/
|
||||
PERMISSION_DENIED,
|
||||
/**
|
||||
* The requested address is already in use.
|
||||
*/
|
||||
ADDRESS_IN_USE,
|
||||
/**
|
||||
* The operation timed out.
|
||||
*/
|
||||
TIMED_OUT,
|
||||
/**
|
||||
* The operation is not supported.
|
||||
*/
|
||||
NOT_SUPPORTED,
|
||||
/**
|
||||
* A protocol error occurred.
|
||||
*/
|
||||
PROTOCOL,
|
||||
/**
|
||||
* A transport-level error occurred.
|
||||
*/
|
||||
TRANSPORT
|
||||
}
|
||||
|
||||
@@ -1141,7 +1334,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.get_frontmost_application}.
|
||||
*/
|
||||
public sealed class FrontmostQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about the application.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1171,7 +1370,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enumerate_applications}, including an optional
|
||||
* set of identifiers to restrict the results to.
|
||||
*/
|
||||
public sealed class ApplicationQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about each application.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1180,14 +1386,30 @@ namespace Frida {
|
||||
|
||||
private Gee.List<string> identifiers = new Gee.ArrayList<string> ();
|
||||
|
||||
/**
|
||||
* Restricts the query to the given application identifier. May be called
|
||||
* multiple times to select several.
|
||||
*
|
||||
* @param identifier the application identifier to include
|
||||
*/
|
||||
public void select_identifier (string identifier) {
|
||||
identifiers.add (identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any identifiers have been selected.
|
||||
*
|
||||
* @return true if the query is restricted to specific identifiers
|
||||
*/
|
||||
public bool has_selected_identifiers () {
|
||||
return !identifiers.is_empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each selected identifier.
|
||||
*
|
||||
* @param func function called with each identifier
|
||||
*/
|
||||
public void enumerate_selected_identifiers (Func<string> func) {
|
||||
foreach (var identifier in identifiers)
|
||||
func (identifier);
|
||||
@@ -1229,7 +1451,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enumerate_processes}, including an optional set
|
||||
* of PIDs to restrict the results to.
|
||||
*/
|
||||
public sealed class ProcessQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about each process.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1238,14 +1467,30 @@ namespace Frida {
|
||||
|
||||
private Gee.List<uint> pids = new Gee.ArrayList<uint> ();
|
||||
|
||||
/**
|
||||
* Restricts the query to the given PID. May be called multiple times to
|
||||
* select several.
|
||||
*
|
||||
* @param pid the process ID to include
|
||||
*/
|
||||
public void select_pid (uint pid) {
|
||||
pids.add (pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any PIDs have been selected.
|
||||
*
|
||||
* @return true if the query is restricted to specific PIDs
|
||||
*/
|
||||
public bool has_selected_pids () {
|
||||
return !pids.is_empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each selected PID.
|
||||
*
|
||||
* @param func function called with each PID
|
||||
*/
|
||||
public void enumerate_selected_pids (Func<uint> func) {
|
||||
foreach (var pid in pids)
|
||||
func (pid);
|
||||
@@ -1287,9 +1532,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How much detail to include in query results.
|
||||
*/
|
||||
public enum Scope {
|
||||
/**
|
||||
* Only the essential fields.
|
||||
*/
|
||||
MINIMAL,
|
||||
/**
|
||||
* Essential fields plus metadata.
|
||||
*/
|
||||
METADATA,
|
||||
/**
|
||||
* Everything available.
|
||||
*/
|
||||
FULL;
|
||||
|
||||
public static Scope from_nick (string nick) throws Error {
|
||||
@@ -1301,6 +1558,67 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enable_spawn_gating}.
|
||||
*/
|
||||
public sealed class SpawnGatingOptions : Object {
|
||||
/**
|
||||
* Which processes to gate. Anything other than DEFAULT requires a Frida new enough to
|
||||
* support scoping, and fails rather than silently gating something broader than asked.
|
||||
*/
|
||||
public SpawnGatingScope scope {
|
||||
get;
|
||||
set;
|
||||
default = DEFAULT;
|
||||
}
|
||||
|
||||
public HashTable<string, Variant> _serialize () {
|
||||
var dict = make_parameters_dict ();
|
||||
|
||||
if (scope != DEFAULT)
|
||||
dict["scope"] = new Variant.string (scope.to_nick ());
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static SpawnGatingOptions _deserialize (HashTable<string, Variant> dict) throws Error {
|
||||
var options = new SpawnGatingOptions ();
|
||||
|
||||
Variant? scope = dict["scope"];
|
||||
if (scope != null) {
|
||||
if (!scope.is_of_type (VariantType.STRING))
|
||||
throw new Error.INVALID_ARGUMENT ("The 'scope' option must be a string");
|
||||
options.scope = SpawnGatingScope.from_nick (scope.get_string ());
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SpawnGatingScope {
|
||||
/**
|
||||
* Leave the choice to the implementation. Sent as a plain enable_spawn_gating call, so
|
||||
* it still works against a Frida too old to know about scopes.
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* Application launches only.
|
||||
*/
|
||||
APPLICATIONS,
|
||||
/**
|
||||
* Every process created on the system. More potent but far more intrusive.
|
||||
*/
|
||||
PROCESSES;
|
||||
|
||||
public static SpawnGatingScope from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<SpawnGatingScope> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<SpawnGatingScope> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HostSpawnOptions {
|
||||
public bool has_argv;
|
||||
public string[] argv;
|
||||
@@ -1371,42 +1689,68 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options controlling how {@link Device.attach} sets up a {@link Session}.
|
||||
*/
|
||||
public sealed class SessionOptions : Object {
|
||||
/**
|
||||
* The realm to attach in.
|
||||
*/
|
||||
public Realm realm {
|
||||
get;
|
||||
set;
|
||||
default = NATIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long the session may survive a disconnection before being torn
|
||||
* down, in seconds; 0 disables persistence.
|
||||
*/
|
||||
public uint persist_timeout {
|
||||
get;
|
||||
set;
|
||||
default = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to a custom agent to use in the emulated realm.
|
||||
*/
|
||||
public string? emulated_agent_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the agent should handle exceptions in the target.
|
||||
*/
|
||||
public Exceptor exceptor {
|
||||
get;
|
||||
set;
|
||||
default = FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to install the unwind broker so exceptions can propagate
|
||||
* through instrumented code.
|
||||
*/
|
||||
public bool unwind_broker {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to monitor the target for exit so the session is cleaned up.
|
||||
*/
|
||||
public bool exit_monitor {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to monitor thread suspension in the target.
|
||||
*/
|
||||
public bool thread_suspend_monitor {
|
||||
get;
|
||||
set;
|
||||
@@ -1415,14 +1759,28 @@ namespace Frida {
|
||||
|
||||
private Gee.List<uint> linker_notifier_offsets = new Gee.ArrayList<uint> ();
|
||||
|
||||
/**
|
||||
* Clears the configured linker notifier offsets.
|
||||
*/
|
||||
public void clear_linker_notifier_offsets () {
|
||||
linker_notifier_offsets.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a linker notifier offset, used to detect module load and unload
|
||||
* events in the target.
|
||||
*
|
||||
* @param offset the offset into the linker to instrument
|
||||
*/
|
||||
public void add_linker_notifier_offset (uint offset) {
|
||||
linker_notifier_offsets.add (offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each configured linker notifier offset.
|
||||
*
|
||||
* @param func function called with each offset
|
||||
*/
|
||||
public void enumerate_linker_notifier_offsets (Func<uint> func) {
|
||||
foreach (var offset in linker_notifier_offsets)
|
||||
func (offset);
|
||||
@@ -1529,8 +1887,18 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How to set up the standard I/O streams of a spawned process.
|
||||
*/
|
||||
public enum Stdio {
|
||||
/**
|
||||
* Inherit the host's streams.
|
||||
*/
|
||||
INHERIT,
|
||||
/**
|
||||
* Pipe the streams through Frida, surfaced via {@link Device.output}
|
||||
* and {@link Device.input}.
|
||||
*/
|
||||
PIPE;
|
||||
|
||||
public static Stdio from_nick (string nick) throws Error {
|
||||
@@ -1594,9 +1962,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How a {@link Child} came to be.
|
||||
*/
|
||||
public enum ChildOrigin {
|
||||
/**
|
||||
* Created by fork().
|
||||
*/
|
||||
FORK,
|
||||
/**
|
||||
* The result of an exec() replacing the image.
|
||||
*/
|
||||
EXEC,
|
||||
/**
|
||||
* Spawned as a new process.
|
||||
*/
|
||||
SPAWN;
|
||||
|
||||
public static ChildOrigin from_nick (string nick) throws Error {
|
||||
@@ -1713,23 +2093,39 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a {@link Script} with {@link Session.create_script}.
|
||||
*/
|
||||
public sealed class ScriptOptions : Object {
|
||||
/**
|
||||
* A name for the script, used in logging and debugging.
|
||||
*/
|
||||
public string? name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* A heap snapshot to start the script from, as produced by
|
||||
* {@link Session.snapshot_script}.
|
||||
*/
|
||||
public Bytes? snapshot {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the snapshot is delivered to the runtime.
|
||||
*/
|
||||
public SnapshotTransport snapshot_transport {
|
||||
get;
|
||||
set;
|
||||
default = INLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime to run the script in.
|
||||
*/
|
||||
public ScriptRuntime runtime {
|
||||
get;
|
||||
set;
|
||||
@@ -1798,17 +2194,35 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How a script's heap snapshot is delivered to the runtime.
|
||||
*/
|
||||
public enum SnapshotTransport {
|
||||
/**
|
||||
* Embed the snapshot inline with the script.
|
||||
*/
|
||||
INLINE,
|
||||
/**
|
||||
* Pass the snapshot through shared memory.
|
||||
*/
|
||||
SHARED_MEMORY
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for building a heap snapshot with {@link Session.snapshot_script}.
|
||||
*/
|
||||
public sealed class SnapshotOptions : Object {
|
||||
/**
|
||||
* Script to run to warm up the runtime before capturing the snapshot.
|
||||
*/
|
||||
public string? warmup_script {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime to capture the snapshot for.
|
||||
*/
|
||||
public ScriptRuntime runtime {
|
||||
get;
|
||||
set;
|
||||
@@ -1848,9 +2262,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime a script should use.
|
||||
*/
|
||||
public enum ScriptRuntime {
|
||||
/**
|
||||
* Let Frida choose.
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* The QuickJS runtime.
|
||||
*/
|
||||
QJS,
|
||||
/**
|
||||
* The V8 runtime.
|
||||
*/
|
||||
V8;
|
||||
|
||||
public static ScriptRuntime from_nick (string nick) throws Error {
|
||||
@@ -1878,17 +2304,29 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Session.join_portal}.
|
||||
*/
|
||||
public sealed class PortalOptions : Object {
|
||||
/**
|
||||
* TLS certificate to present to the portal.
|
||||
*/
|
||||
public TlsCertificate? certificate {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication token to present to the portal.
|
||||
*/
|
||||
public string? token {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access-control tags scoping what this membership may reach.
|
||||
*/
|
||||
public string[]? acl {
|
||||
get;
|
||||
set;
|
||||
@@ -1941,7 +2379,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Session.setup_peer_connection}, configuring how the
|
||||
* peer-to-peer connection is negotiated.
|
||||
*/
|
||||
public sealed class PeerOptions : Object {
|
||||
/**
|
||||
* The STUN server to use for discovering the public address, if any.
|
||||
*/
|
||||
public string? stun_server {
|
||||
get;
|
||||
set;
|
||||
@@ -1949,14 +2394,27 @@ namespace Frida {
|
||||
|
||||
private Gee.List<Relay> relays = new Gee.ArrayList<Relay> ();
|
||||
|
||||
/**
|
||||
* Removes all configured relays.
|
||||
*/
|
||||
public void clear_relays () {
|
||||
relays.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a relay to use when a direct connection cannot be established.
|
||||
*
|
||||
* @param relay the relay to add
|
||||
*/
|
||||
public void add_relay (Relay relay) {
|
||||
relays.add (relay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each configured relay.
|
||||
*
|
||||
* @param func function called with each relay
|
||||
*/
|
||||
public void enumerate_relays (Func<Relay> func) {
|
||||
foreach (var relay in relays)
|
||||
func (relay);
|
||||
@@ -2002,27 +2460,51 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A TURN relay used to establish a peer-to-peer connection when a direct
|
||||
* one is not possible.
|
||||
*/
|
||||
public sealed class Relay : Object {
|
||||
/**
|
||||
* The relay's address, as host and port.
|
||||
*/
|
||||
public string address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The username to authenticate with.
|
||||
*/
|
||||
public string username {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password to authenticate with.
|
||||
*/
|
||||
public string password {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of relay and transport to use.
|
||||
*/
|
||||
public RelayKind kind {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a relay.
|
||||
*
|
||||
* @param address the relay's address, as host and port
|
||||
* @param username the username to authenticate with
|
||||
* @param password the password to authenticate with
|
||||
* @param kind the kind of relay and transport
|
||||
*/
|
||||
public Relay (string address, string username, string password, RelayKind kind) {
|
||||
Object (
|
||||
address: address,
|
||||
@@ -2049,9 +2531,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of a {@link Relay} used for peer-to-peer connections.
|
||||
*/
|
||||
public enum RelayKind {
|
||||
/**
|
||||
* A TURN relay reached over UDP.
|
||||
*/
|
||||
TURN_UDP,
|
||||
/**
|
||||
* A TURN relay reached over TCP.
|
||||
*/
|
||||
TURN_TCP,
|
||||
/**
|
||||
* A TURN relay reached over TLS.
|
||||
*/
|
||||
TURN_TLS
|
||||
}
|
||||
|
||||
|
||||
@@ -97,42 +97,77 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the network endpoint a {@link PortalService} or
|
||||
* {@link ControlService} listens on.
|
||||
*/
|
||||
public sealed class EndpointParameters : Object {
|
||||
/**
|
||||
* The address to bind to, or null for the default.
|
||||
*/
|
||||
public string? address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The port to listen on, or 0 for the default.
|
||||
*/
|
||||
public uint16 port {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* TLS certificate to serve, enabling TLS when set.
|
||||
*/
|
||||
public TlsCertificate? certificate {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Origin to require from connecting clients, if any.
|
||||
*/
|
||||
public string? origin {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service used to authenticate connecting clients, if any.
|
||||
*/
|
||||
public AuthenticationService? auth_service {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory whose files are served as static web assets, if any.
|
||||
*/
|
||||
public File? asset_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler invoked for web requests, if any.
|
||||
*/
|
||||
public WebRequestHandler? request_handler {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates endpoint parameters.
|
||||
*
|
||||
* @param address the address to bind to, or null for the default
|
||||
* @param port the port to listen on, or 0 for the default
|
||||
* @param certificate TLS certificate to serve, or null for plaintext
|
||||
* @param origin origin to require from clients, or null
|
||||
* @param auth_service service used to authenticate clients, or null
|
||||
* @param asset_root directory of static assets to serve, or null
|
||||
*/
|
||||
public EndpointParameters (string? address = null, uint16 port = 0, TlsCertificate? certificate = null,
|
||||
string? origin = null, AuthenticationService? auth_service = null, File? asset_root = null) {
|
||||
Object (
|
||||
@@ -146,26 +181,51 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles web requests made to an endpoint's served assets.
|
||||
*/
|
||||
public interface WebRequestHandler : Object {
|
||||
/**
|
||||
* Handles a single web request.
|
||||
*
|
||||
* @param request the incoming request
|
||||
* @return the response to send, or null to fall through to the default
|
||||
* handling
|
||||
*/
|
||||
public abstract async WebResponse? handle_request (WebRequest request, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
/**
|
||||
* An incoming web request passed to a {@link WebRequestHandler}.
|
||||
*/
|
||||
public sealed class WebRequest : Object {
|
||||
/**
|
||||
* The HTTP method, such as `GET` or `POST`.
|
||||
*/
|
||||
public string method {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request path.
|
||||
*/
|
||||
public string path {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw query string, if any.
|
||||
*/
|
||||
public string? query_string {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request body, if any.
|
||||
*/
|
||||
public Bytes? body {
|
||||
get;
|
||||
construct;
|
||||
@@ -178,6 +238,11 @@ namespace Frida {
|
||||
raw_headers = headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each request header.
|
||||
*
|
||||
* @param func function called with each header's name and value
|
||||
*/
|
||||
public void foreach_header (WebRequestHeaderFunc func) {
|
||||
raw_headers.foreach ((name, val) => {
|
||||
func (name, val);
|
||||
@@ -185,14 +250,29 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked for each header while iterating a {@link WebRequest}.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param val the header value
|
||||
*/
|
||||
public delegate void WebRequestHeaderFunc (string name, string val);
|
||||
|
||||
/**
|
||||
* A response returned from a {@link WebRequestHandler}.
|
||||
*/
|
||||
public sealed class WebResponse : Object {
|
||||
/**
|
||||
* The HTTP status code.
|
||||
*/
|
||||
public uint status {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response body.
|
||||
*/
|
||||
public Bytes body {
|
||||
get;
|
||||
construct;
|
||||
@@ -200,10 +280,22 @@ namespace Frida {
|
||||
|
||||
private Soup.MessageHeaders raw_headers = new Soup.MessageHeaders (RESPONSE);
|
||||
|
||||
/**
|
||||
* Creates a response.
|
||||
*
|
||||
* @param status the HTTP status code
|
||||
* @param body the response body
|
||||
*/
|
||||
public WebResponse (uint status, Bytes body) {
|
||||
Object (status: status, body: body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header to the response.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param val the header value
|
||||
*/
|
||||
public void add_header (string name, string val) {
|
||||
raw_headers.append (name, val);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace Frida {
|
||||
public string? name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The XNU Mach trap numbers, as used on Apple platforms.
|
||||
*/
|
||||
public enum XnuMachTrap {
|
||||
MACH_VM_ALLOCATE = -10,
|
||||
MACH_VM_PURGABLE_CONTROL = -11,
|
||||
@@ -81,6 +84,9 @@ namespace Frida {
|
||||
IOKIT_USER_CLIENT = -100,
|
||||
}
|
||||
|
||||
/**
|
||||
* The XNU BSD syscall numbers, as used on Apple platforms.
|
||||
*/
|
||||
public enum XnuBsdSyscall {
|
||||
EXIT = 1,
|
||||
FORK = 2,
|
||||
|
||||
@@ -1810,6 +1810,11 @@ namespace Frida.Gadget {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() id=%s close_request=%p", id.handle, close_request);
|
||||
|
||||
while (close_request != null) {
|
||||
try {
|
||||
yield close_request.future.wait_async (cancellable);
|
||||
@@ -85,6 +87,8 @@ namespace Frida {
|
||||
|
||||
yield transmitter.close (cancellable);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() done id=%s", id.handle);
|
||||
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
@@ -179,6 +183,18 @@ namespace Frida {
|
||||
yield script_engine.load_script (script_id);
|
||||
}
|
||||
|
||||
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
script_engine.interrupt_script (script_id);
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
yield script_engine.terminate_script (script_id);
|
||||
}
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
@@ -264,6 +280,8 @@ namespace Frida {
|
||||
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
|
||||
transmitter.candidate_gathering_done.disconnect (on_transmitter_candidate_gathering_done);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession emitting closed id=%s", id.handle);
|
||||
|
||||
closed ();
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,14 @@ namespace Frida {
|
||||
yield get_instance (script_id).load ();
|
||||
}
|
||||
|
||||
public void interrupt_script (AgentScriptId script_id) throws Error {
|
||||
get_instance (script_id).interrupt ();
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id) throws Error {
|
||||
yield get_instance (script_id).terminate ();
|
||||
}
|
||||
|
||||
public Gum.Script eternalize_script (AgentScriptId script_id) throws Error {
|
||||
var instance = get_instance (script_id);
|
||||
|
||||
@@ -354,6 +362,16 @@ namespace Frida {
|
||||
load_request.resolve (true);
|
||||
}
|
||||
|
||||
public void interrupt () {
|
||||
script.interrupt ();
|
||||
}
|
||||
|
||||
public async void terminate () {
|
||||
script.interrupt ();
|
||||
|
||||
yield close ();
|
||||
}
|
||||
|
||||
private Promise<bool> unload () {
|
||||
var request = new Promise<bool> ();
|
||||
|
||||
|
||||
+25
-20
@@ -130,6 +130,10 @@ if host_os_family == 'darwin'
|
||||
add_languages('objc', 'objcpp', native: false)
|
||||
endif
|
||||
|
||||
if meson.is_cross_build()
|
||||
add_languages('c', 'cpp', 'vala', native: true)
|
||||
endif
|
||||
|
||||
python = import('python').find_installation()
|
||||
|
||||
if cc.get_argument_syntax() == 'msvc'
|
||||
@@ -329,18 +333,6 @@ if get_option('b_sanitize') == 'address'
|
||||
cdata.set('HAVE_ASAN', 1)
|
||||
endif
|
||||
|
||||
have_in_tree_payloads = get_option('default_library') != 'shared'
|
||||
gum_dep = dependency('frida-gum-1.0', default_options: [
|
||||
'default_library=static',
|
||||
f'frida_version=@frida_version@',
|
||||
'gumjs=' + (have_in_tree_payloads ? 'enabled' : 'disabled'),
|
||||
'inspector=enabled',
|
||||
])
|
||||
if have_in_tree_payloads
|
||||
gumjs_dep = dependency('frida-gumjs-1.0', default_options: ['default_library=static'])
|
||||
endif
|
||||
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
|
||||
|
||||
glib_version_req = '>=2.74'
|
||||
glib_options = [
|
||||
'default_library=static',
|
||||
@@ -359,14 +351,6 @@ if optimize_for_prod
|
||||
]
|
||||
endif
|
||||
|
||||
gee_options = [
|
||||
'default_library=static',
|
||||
'disable-introspection=true',
|
||||
]
|
||||
if optimize_for_prod
|
||||
gee_options += 'disable-internal-asserts=true'
|
||||
endif
|
||||
|
||||
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
|
||||
if glib_dep.type_name() == 'internal'
|
||||
have_shared_glib = get_option('default_library') != 'static'
|
||||
@@ -385,6 +369,27 @@ if have_frida_glib
|
||||
cdata.set('HAVE_FRIDA_GLIB', 1)
|
||||
vala_flags += '--define=HAVE_FRIDA_GLIB'
|
||||
endif
|
||||
|
||||
have_in_tree_payloads = not have_shared_glib
|
||||
gum_dep = dependency('frida-gum-1.0', default_options: [
|
||||
'default_library=static',
|
||||
f'frida_version=@frida_version@',
|
||||
'gumjs=' + (have_in_tree_payloads ? 'enabled' : 'disabled'),
|
||||
'inspector=enabled',
|
||||
])
|
||||
if have_in_tree_payloads
|
||||
gumjs_dep = dependency('frida-gumjs-1.0', default_options: ['default_library=static'])
|
||||
endif
|
||||
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
|
||||
|
||||
gee_options = [
|
||||
'default_library=static',
|
||||
'disable-introspection=true',
|
||||
]
|
||||
if optimize_for_prod
|
||||
gee_options += 'disable-internal-asserts=true'
|
||||
endif
|
||||
|
||||
gobject_dep = dependency('gobject-2.0')
|
||||
gmodule_dep = dependency('gmodule-2.0')
|
||||
gio_dep = dependency('gio-2.0')
|
||||
|
||||
+1
-1
Submodule releng updated: 1442ae5a7a...abb8238970.
+87
-4
@@ -12,6 +12,7 @@ namespace Frida.Server {
|
||||
private static string? directory = null;
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool daemonize = false;
|
||||
private static string? output_path = null;
|
||||
#endif
|
||||
private static string? softener_flavor_str = null;
|
||||
private static bool enable_preload = true;
|
||||
@@ -42,6 +43,7 @@ namespace Frida.Server {
|
||||
{ "directory", 'd', 0, OptionArg.STRING, ref directory, "Store binaries in DIRECTORY", "DIRECTORY" },
|
||||
#if !WINDOWS && !TVOS
|
||||
{ "daemonize", 'D', 0, OptionArg.NONE, ref daemonize, "Detach and become a daemon", null },
|
||||
{ "output", 'o', 0, OptionArg.FILENAME, ref output_path, "Redirect output to FILE", "FILE" },
|
||||
#endif
|
||||
{ "policy-softener", 0, 0, OptionArg.STRING, ref softener_flavor_str, "Select policy softener", "system|internal" },
|
||||
{ "disable-preload", 'P', OptionFlags.REVERSE, OptionArg.NONE, ref enable_preload, "Disable preload optimization", null },
|
||||
@@ -78,6 +80,13 @@ namespace Frida.Server {
|
||||
|
||||
Environment.set_verbose_logging_enabled (verbose);
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (output_path != null && !daemonize) {
|
||||
if (!redirect_output_to (output_path))
|
||||
return 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
EndpointParameters endpoint_params;
|
||||
try {
|
||||
endpoint_params = new EndpointParameters (listen_address, 0, parse_certificate (certpath), origin,
|
||||
@@ -156,12 +165,15 @@ namespace Frida.Server {
|
||||
Posix.setsid ();
|
||||
|
||||
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_in, Posix.STDIN_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_in);
|
||||
Posix.close (null_out);
|
||||
|
||||
if (output_path == null || !redirect_output_to (output_path)) {
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_out);
|
||||
}
|
||||
}
|
||||
|
||||
var status = new uint8[1];
|
||||
@@ -218,6 +230,22 @@ namespace Frida.Server {
|
||||
return application.run ();
|
||||
}
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool redirect_output_to (string path) {
|
||||
int fd = Posix.open (path, Posix.O_WRONLY | Posix.O_CREAT | Posix.O_APPEND, 0644);
|
||||
if (fd == -1) {
|
||||
printerr ("Unable to open “%s”: %s\n", path, Posix.strerror (Posix.errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
Posix.dup2 (fd, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (fd, Posix.STDERR_FILENO);
|
||||
Posix.close (fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Environment {
|
||||
public extern void init ();
|
||||
public extern void set_verbose_logging_enabled (bool enabled);
|
||||
@@ -274,6 +302,15 @@ namespace Frida.Server {
|
||||
return false;
|
||||
});
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (!daemonize)
|
||||
process_commands.begin ();
|
||||
#elif !WINDOWS
|
||||
process_commands.begin ();
|
||||
#endif
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
loop.run ();
|
||||
@@ -281,6 +318,45 @@ namespace Frida.Server {
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
// TEMPORARY DEBUG HACK: type “t” + Enter to dump alive GTasks.
|
||||
private async void process_commands () {
|
||||
var input = new DataInputStream (new UnixInputStream (Posix.STDIN_FILENO, false));
|
||||
|
||||
while (true) {
|
||||
string? line;
|
||||
try {
|
||||
line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
if (line == null)
|
||||
return;
|
||||
|
||||
switch (line.strip ()) {
|
||||
case "t":
|
||||
g_task_print_alive_tasks ();
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
printerr ("Unknown command; try “t”\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (MainContext.default ());
|
||||
}
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
if (device_id != null && device_id != "local") {
|
||||
@@ -355,4 +431,11 @@ namespace Frida.Server {
|
||||
|
||||
return new TlsCertificate.from_file (path);
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_print_alive_tasks")]
|
||||
private extern void g_task_print_alive_tasks ();
|
||||
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
+327
-2
@@ -89,7 +89,7 @@ def main():
|
||||
emit_header(api, output_dir)
|
||||
|
||||
if enable_gir:
|
||||
emit_gir(api, core_gir, base_gir, output_dir)
|
||||
emit_gir(api, core_gir, base_gir, output_dir, build_doc_index(src_dir))
|
||||
|
||||
if enable_vapi:
|
||||
emit_vapi(api, output_dir)
|
||||
@@ -195,7 +195,7 @@ def emit_header(api, output_dir):
|
||||
|
||||
output_header_file.write("\n\n#endif\n")
|
||||
|
||||
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> str:
|
||||
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs: DocIndex) -> str:
|
||||
ET.register_namespace("", CORE_NAMESPACE)
|
||||
ET.register_namespace("c", C_NAMESPACE)
|
||||
ET.register_namespace("glib", GLIB_NAMESPACE)
|
||||
@@ -264,6 +264,8 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
if callback.get("name") in needed_names:
|
||||
merged_namespace.append(callback)
|
||||
|
||||
inject_documentation(merged_namespace, docs)
|
||||
|
||||
ET.indent(merged_root, space=" ")
|
||||
result = ET.tostring(merged_root,
|
||||
encoding="unicode",
|
||||
@@ -275,6 +277,328 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
|
||||
return [elem for elem in elements if elem.get("name") in spec_set]
|
||||
|
||||
@dataclass
|
||||
class DocComment:
|
||||
body: str
|
||||
params: dict
|
||||
returns: str
|
||||
filename: str = "<generated>"
|
||||
line: int = 0
|
||||
|
||||
@dataclass
|
||||
class DocIndex:
|
||||
types: dict # type_name -> DocComment
|
||||
members: dict # (type_name, member_name) -> DocComment
|
||||
enum_members: dict # (enum_name, member_name) -> DocComment
|
||||
|
||||
DOC_TYPE_PATTERN = re.compile(r"public\s+(?:sealed\s+|abstract\s+)?(class|interface|enum|errordomain)\s+(\w+)")
|
||||
DOC_SIGNAL_PATTERN = re.compile(r"public\s+signal\s+\S.*?\b(\w+)\s*\(")
|
||||
DOC_PROPERTY_PATTERN = re.compile(r"public\s+(?:unowned\s+)?\S.*?\b(\w+)\s*\{")
|
||||
DOC_METHOD_PATTERN = re.compile(r"public\s+\S.*?\b(\w+)\s*\(")
|
||||
DOC_ENUM_MEMBER_PATTERN = re.compile(r"([A-Z][A-Z0-9_]*)\b")
|
||||
|
||||
def build_doc_index(src_dir: Path) -> DocIndex:
|
||||
repo_root = src_dir.parent
|
||||
sources = []
|
||||
for name in TOPLEVEL_NAMES:
|
||||
path = src_dir / name
|
||||
if path.exists():
|
||||
sources.append(path)
|
||||
base_dir = src_dir.parent / "lib" / "base"
|
||||
if base_dir.is_dir():
|
||||
sources.extend(sorted(base_dir.rglob("*.vala")))
|
||||
|
||||
index = DocIndex(types={}, members={}, enum_members={})
|
||||
for path in sources:
|
||||
try:
|
||||
filename = path.relative_to(repo_root).as_posix()
|
||||
except ValueError:
|
||||
filename = path.name
|
||||
_collect_docs_from_source(path.read_text(encoding="utf-8"), index, filename)
|
||||
return index
|
||||
|
||||
def _collect_docs_from_source(source: str, index: DocIndex, filename: str):
|
||||
lines = source.split("\n")
|
||||
pending = None
|
||||
current_type = None
|
||||
current_is_enum = False
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
|
||||
if stripped.startswith("/**"):
|
||||
start_line = i + 1
|
||||
block = [line]
|
||||
while "*/" not in lines[i]:
|
||||
i += 1
|
||||
block.append(lines[i])
|
||||
pending = _parse_doc_block(block)
|
||||
pending.filename = filename
|
||||
pending.line = start_line
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped == "" or stripped.startswith("//") or stripped.startswith("["):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
indent = len(line) - len(line.lstrip("\t"))
|
||||
|
||||
type_match = DOC_TYPE_PATTERN.match(stripped)
|
||||
if indent == 1 and type_match is not None:
|
||||
current_type = type_match.group(2)
|
||||
current_is_enum = type_match.group(1) in ("enum", "errordomain")
|
||||
if pending is not None:
|
||||
index.types[current_type] = pending
|
||||
pending = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if pending is not None and current_type is not None and indent >= 2:
|
||||
if current_is_enum:
|
||||
member_match = DOC_ENUM_MEMBER_PATTERN.match(stripped)
|
||||
if member_match is not None:
|
||||
index.enum_members[(current_type, member_match.group(1))] = pending
|
||||
else:
|
||||
name = _parse_member_name(stripped, current_type)
|
||||
if name is not None:
|
||||
index.members[(current_type, name)] = pending
|
||||
pending = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
pending = None
|
||||
i += 1
|
||||
|
||||
def _parse_member_name(stripped: str, type_name: str):
|
||||
if not stripped.startswith("public"):
|
||||
return None
|
||||
signal_match = DOC_SIGNAL_PATTERN.match(stripped)
|
||||
if signal_match is not None:
|
||||
return signal_match.group(1)
|
||||
brace = stripped.find("{")
|
||||
if brace != -1 and "(" not in stripped[:brace]:
|
||||
property_match = DOC_PROPERTY_PATTERN.match(stripped)
|
||||
if property_match is not None:
|
||||
return property_match.group(1)
|
||||
ctor_match = re.match(r"public\s+" + re.escape(type_name) + r"(?:\.(\w+))?\s*\(", stripped)
|
||||
if ctor_match is not None:
|
||||
return "new" if ctor_match.group(1) is None else ctor_match.group(1)
|
||||
method_match = DOC_METHOD_PATTERN.match(stripped)
|
||||
if method_match is not None:
|
||||
return method_match.group(1)
|
||||
return None
|
||||
|
||||
def _parse_doc_block(block: List[str]) -> DocComment:
|
||||
text_lines = []
|
||||
for raw in block:
|
||||
s = raw.strip()
|
||||
if s.startswith("/**"):
|
||||
s = s[3:]
|
||||
if s.endswith("*/"):
|
||||
s = s[:-2]
|
||||
s = s.strip()
|
||||
if s.startswith("*"):
|
||||
s = s[1:]
|
||||
if s.startswith(" "):
|
||||
s = s[1:]
|
||||
text_lines.append(s.rstrip())
|
||||
|
||||
# Valadoc block tags: "@param <name> <description>" and "@return <description>".
|
||||
body_lines = []
|
||||
params = {}
|
||||
returns_lines = []
|
||||
target = body_lines
|
||||
for line in text_lines:
|
||||
param_match = re.match(r"@param\s+(\w+)\s*(.*)", line)
|
||||
return_match = re.match(r"@returns?\s*(.*)", line)
|
||||
if param_match is not None:
|
||||
params[param_match.group(1)] = [param_match.group(2)]
|
||||
target = params[param_match.group(1)]
|
||||
continue
|
||||
if return_match is not None:
|
||||
returns_lines = [return_match.group(1)]
|
||||
target = returns_lines
|
||||
continue
|
||||
target.append(line)
|
||||
|
||||
def finish(lines):
|
||||
return _valadoc_to_markdown("\n".join(lines).strip())
|
||||
|
||||
return DocComment(
|
||||
body=finish(body_lines),
|
||||
params={k: finish(v) for k, v in params.items()},
|
||||
returns=finish(returns_lines),
|
||||
)
|
||||
|
||||
def _valadoc_to_markdown(text: str) -> str:
|
||||
if not text:
|
||||
return text
|
||||
# Code blocks: {{{ ... }}} -> fenced ``` blocks.
|
||||
def code_block(match):
|
||||
code = match.group(1).strip("\n")
|
||||
return "\n```\n" + code + "\n```\n"
|
||||
text = re.sub(r"\{\{\{(.*?)\}\}\}", code_block, text, flags=re.DOTALL)
|
||||
# Note: {@link ...} cross-references are resolved later, at injection time,
|
||||
# where the full symbol table is available (see inject_documentation).
|
||||
# Inline monospace: ``x`` -> `x` (valadoc) is already markdown-compatible.
|
||||
# Bold: ''x'' -> **x**.
|
||||
text = re.sub(r"''(.+?)''", r"**\1**", text)
|
||||
# Italic: //x// -> *x*, but leave protocol-relative URLs (://) alone.
|
||||
text = re.sub(r"(?<![:/])//(?!/)(.+?)//", r"*\1*", text)
|
||||
return text
|
||||
|
||||
def inject_documentation(merged_namespace: ET.Element, docs: DocIndex):
|
||||
glib_signal_tag = f"{{{GLIB_NAMESPACE}}}signal"
|
||||
callable_tags = {
|
||||
f"{{{CORE_NAMESPACE}}}method",
|
||||
f"{{{CORE_NAMESPACE}}}constructor",
|
||||
f"{{{CORE_NAMESPACE}}}function",
|
||||
f"{{{CORE_NAMESPACE}}}virtual-method",
|
||||
glib_signal_tag,
|
||||
}
|
||||
property_tag = f"{{{CORE_NAMESPACE}}}property"
|
||||
member_tag = f"{{{CORE_NAMESPACE}}}member"
|
||||
doc_tag = f"{{{CORE_NAMESPACE}}}doc"
|
||||
|
||||
# Build a symbol table so {@link ...} can be turned into real gi-docgen
|
||||
# cross-references. Keys use Vala-style names (underscores); values carry the
|
||||
# gi-docgen link kind and the GIR name (dashes for properties/signals).
|
||||
type_kinds = {} # type_name -> gi-docgen kind ("class"/"iface"/"enum"/"error")
|
||||
member_kinds = {} # (type_name, vala_member) -> (kind, gir_name)
|
||||
for type_elem in list(merged_namespace):
|
||||
type_name = type_elem.get("name")
|
||||
if type_name is None:
|
||||
continue
|
||||
tag = type_elem.tag.split("}")[-1]
|
||||
if tag == "class":
|
||||
type_kinds[type_name] = "class"
|
||||
elif tag == "interface":
|
||||
type_kinds[type_name] = "iface"
|
||||
elif tag == "enumeration":
|
||||
type_kinds[type_name] = "error" \
|
||||
if type_elem.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None \
|
||||
else "enum"
|
||||
for child in list(type_elem):
|
||||
cname = child.get("name")
|
||||
if cname is None:
|
||||
continue
|
||||
if child.tag == f"{{{CORE_NAMESPACE}}}method":
|
||||
member_kinds[(type_name, cname)] = ("method", cname)
|
||||
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor":
|
||||
member_kinds[(type_name, cname)] = ("ctor", cname)
|
||||
elif child.tag == property_tag:
|
||||
member_kinds[(type_name, cname.replace("-", "_"))] = ("property", cname)
|
||||
elif child.tag == glib_signal_tag:
|
||||
member_kinds[(type_name, cname.replace("-", "_"))] = ("signal", cname)
|
||||
|
||||
def resolve_links(text: str) -> str:
|
||||
def repl(match):
|
||||
target = re.sub(r"^Frida\.", "", match.group(1).strip())
|
||||
parts = target.split(".")
|
||||
if len(parts) == 1:
|
||||
kind = type_kinds.get(parts[0])
|
||||
if kind is not None:
|
||||
return f"[{kind}@Frida.{parts[0]}]"
|
||||
elif len(parts) == 2:
|
||||
info = member_kinds.get((parts[0], parts[1]))
|
||||
if info is not None:
|
||||
kind, gir_name = info
|
||||
if kind == "method":
|
||||
return f"[method@Frida.{parts[0]}.{gir_name}]"
|
||||
if kind == "ctor":
|
||||
return f"[ctor@Frida.{parts[0]}.{gir_name}]"
|
||||
if kind == "property":
|
||||
return f"[property@Frida.{parts[0]}:{gir_name}]"
|
||||
if kind == "signal":
|
||||
return f"[signal@Frida.{parts[0]}::{gir_name}]"
|
||||
return "`" + target + "`"
|
||||
return re.sub(r"\{@link\s+([^}]+)\}", repl, text)
|
||||
|
||||
def set_doc(elem: ET.Element, text: str, filename: str = "<generated>",
|
||||
line: int = 0):
|
||||
if not text:
|
||||
return
|
||||
if elem.find(doc_tag) is not None:
|
||||
return
|
||||
doc = ET.Element(doc_tag)
|
||||
doc.set("xml:space", "preserve")
|
||||
# gi-docgen accesses filename/line unconditionally, so always emit them,
|
||||
# mirroring what g-ir-scanner produces for hand-written C.
|
||||
doc.set("filename", filename)
|
||||
doc.set("line", str(line))
|
||||
doc.text = resolve_links(text)
|
||||
elem.insert(0, doc)
|
||||
|
||||
def member_doc(type_name: str, name: str):
|
||||
comment = docs.members.get((type_name, name))
|
||||
if comment is None:
|
||||
for suffix in ("_finish", "_sync"):
|
||||
if name.endswith(suffix):
|
||||
comment = docs.members.get((type_name, name[: -len(suffix)]))
|
||||
if comment is not None:
|
||||
break
|
||||
return comment
|
||||
|
||||
def apply_callable(elem: ET.Element, comment: DocComment):
|
||||
set_doc(elem, comment.body, comment.filename, comment.line)
|
||||
if comment.returns:
|
||||
rv = elem.find(f"{{{CORE_NAMESPACE}}}return-value")
|
||||
if rv is not None:
|
||||
set_doc(rv, comment.returns, comment.filename, comment.line)
|
||||
params_elem = elem.find(f"{{{CORE_NAMESPACE}}}parameters")
|
||||
if params_elem is not None and comment.params:
|
||||
for param in params_elem.findall(f"{{{CORE_NAMESPACE}}}parameter"):
|
||||
text = comment.params.get(param.get("name"))
|
||||
if text:
|
||||
set_doc(param, text, comment.filename, comment.line)
|
||||
|
||||
for type_elem in list(merged_namespace):
|
||||
type_name = type_elem.get("name")
|
||||
if type_name is None:
|
||||
continue
|
||||
tag = type_elem.tag.split("}")[-1]
|
||||
|
||||
type_comment = docs.types.get(type_name)
|
||||
if type_comment is not None and tag in ("class", "interface", "enumeration"):
|
||||
set_doc(type_elem, type_comment.body, type_comment.filename,
|
||||
type_comment.line)
|
||||
|
||||
for child in list(type_elem):
|
||||
child_name = child.get("name")
|
||||
if child_name is None:
|
||||
continue
|
||||
if child.tag in callable_tags:
|
||||
lookup = child_name.replace("-", "_") if child.tag == glib_signal_tag \
|
||||
else child_name
|
||||
comment = member_doc(type_name, lookup)
|
||||
if comment is not None:
|
||||
apply_callable(child, comment)
|
||||
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor" \
|
||||
and child_name == "new":
|
||||
# Vala's implicit default constructor; give it a sensible
|
||||
# description rather than leaving it bare.
|
||||
set_doc(child, f"Creates a new [class@Frida.{type_name}].")
|
||||
elif child.tag == property_tag:
|
||||
vala_name = child_name.replace("-", "_")
|
||||
comment = docs.members.get((type_name, vala_name))
|
||||
if comment is not None:
|
||||
set_doc(child, comment.body, comment.filename, comment.line)
|
||||
# The C API also exposes the property through getter/setter
|
||||
# methods; give them the same description.
|
||||
accessors = {"get_" + vala_name, "set_" + vala_name}
|
||||
for sibling in type_elem:
|
||||
if sibling.tag == f"{{{CORE_NAMESPACE}}}method" \
|
||||
and sibling.get("name") in accessors:
|
||||
set_doc(sibling, comment.body, comment.filename,
|
||||
comment.line)
|
||||
elif child.tag == member_tag:
|
||||
comment = docs.enum_members.get((type_name, child_name.upper()))
|
||||
if comment is not None:
|
||||
set_doc(child, comment.body, comment.filename, comment.line)
|
||||
|
||||
def emit_vapi(api, output_dir):
|
||||
with OutputFile(output_dir / f"frida-core-{api.version}.vapi") as output_vapi_file:
|
||||
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
|
||||
@@ -337,6 +661,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
enum_types = []
|
||||
|
||||
base_public_types = {
|
||||
"SpawnGatingOptions": "ProcessMatchOptions",
|
||||
"FrontmostQueryOptions": "SpawnOptions",
|
||||
"ApplicationQueryOptions": "FrontmostQueryOptions",
|
||||
"ProcessQueryOptions": "ApplicationQueryOptions",
|
||||
|
||||
@@ -286,6 +286,11 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
@@ -643,6 +648,14 @@ namespace Frida {
|
||||
|
||||
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -8,7 +8,7 @@
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.5.0",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
@@ -439,9 +439,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "19.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.5.0.tgz",
|
||||
"integrity": "sha512-aka2Jihv/tsmvgffeABBnRueGiz4YILaDAnHsK2EbXS4wIGNxy/OsdzWwIRfvWXwaAju+FKfFFI+q8mG4Jmm1w==",
|
||||
"version": "19.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.10.0.tgz",
|
||||
"integrity": "sha512-GBKAzwmamtNPBXEcIve1yJ6BB9ZgyW9al3+f06S+lDAvennOx9GfFQ9RDY9kP5nJrkiiRHLzgbDFjQ/+g+iw1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.5.0",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace Frida.Barebone {
|
||||
|
||||
protected override async void enable_extensions (Cancellable? cancellable) throws Error, IOError {
|
||||
yield execute_simple ("QThreadSuffixSupported", cancellable);
|
||||
supported_features.add ("thread-suffix");
|
||||
yield execute_simple ("QListThreadsInStopReply", cancellable);
|
||||
|
||||
yield discover_target (cancellable);
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Compiles a TypeScript or JavaScript project into a single script bundle
|
||||
* that can be loaded with {@link Session.create_script}.
|
||||
*/
|
||||
public sealed class Compiler : Object {
|
||||
/**
|
||||
* Emitted when a build is starting.
|
||||
*/
|
||||
public signal void starting ();
|
||||
/**
|
||||
* Emitted when a build has finished.
|
||||
*/
|
||||
public signal void finished ();
|
||||
/**
|
||||
* Emitted with the freshly built bundle, primarily when watching.
|
||||
*
|
||||
* @param bundle the compiled script bundle
|
||||
*/
|
||||
public signal void output (string bundle);
|
||||
/**
|
||||
* Emitted with compiler diagnostics, such as type errors and warnings.
|
||||
*
|
||||
* @param diagnostics the diagnostics, as a variant
|
||||
*/
|
||||
public signal void diagnostics (Variant diagnostics);
|
||||
|
||||
private size_t watch_session_handle = 0;
|
||||
@@ -11,6 +31,11 @@ namespace Frida {
|
||||
private MainContext main_context;
|
||||
|
||||
// TODO: Remove the DeviceManager parameter.
|
||||
/**
|
||||
* Creates a compiler.
|
||||
*
|
||||
* @param manager unused; kept for compatibility
|
||||
*/
|
||||
public Compiler (DeviceManager? manager = null) {
|
||||
Object ();
|
||||
}
|
||||
@@ -27,6 +52,13 @@ namespace Frida {
|
||||
cancel_watch ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the project rooted at @entrypoint once.
|
||||
*
|
||||
* @param entrypoint path to the project's entrypoint module
|
||||
* @param options build options, or null for the defaults
|
||||
* @return the compiled script bundle
|
||||
*/
|
||||
public async string build (string entrypoint, BuildOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
CompilerBackend.check_available ();
|
||||
@@ -78,6 +110,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the project and keeps rebuilding it as its sources change,
|
||||
* emitting {@link Compiler.output} with each fresh bundle.
|
||||
*
|
||||
* @param entrypoint path to the project's entrypoint module
|
||||
* @param options watch options, or null for the defaults
|
||||
*/
|
||||
public async void watch (string entrypoint, WatchOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
CompilerBackend.check_available ();
|
||||
@@ -980,42 +1019,66 @@ namespace Frida {
|
||||
return Environment.get_current_dir ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Options shared by {@link Compiler.build} and {@link Compiler.watch}.
|
||||
*/
|
||||
public class CompilerOptions : Object {
|
||||
/**
|
||||
* The project root directory, or null to infer it from the entrypoint.
|
||||
*/
|
||||
public string? project_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the resulting bundle's bytes are encoded.
|
||||
*/
|
||||
public OutputFormat output_format {
|
||||
get;
|
||||
set;
|
||||
default = UNESCAPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module format of the bundle.
|
||||
*/
|
||||
public BundleFormat bundle_format {
|
||||
get;
|
||||
set;
|
||||
default = ESM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to type-check the project.
|
||||
*/
|
||||
public TypeCheckMode type_check {
|
||||
get;
|
||||
set;
|
||||
default = FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to include source maps in the bundle.
|
||||
*/
|
||||
public SourceMaps source_maps {
|
||||
get;
|
||||
set;
|
||||
default = INCLUDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which compressor to run the output through, if any.
|
||||
*/
|
||||
public JsCompression compression {
|
||||
get;
|
||||
set;
|
||||
default = NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which platform the bundle targets.
|
||||
*/
|
||||
public JsPlatform platform {
|
||||
get;
|
||||
set;
|
||||
@@ -1024,29 +1087,60 @@ namespace Frida {
|
||||
|
||||
internal Gee.List<string> externals = new Gee.ArrayList<string> ();
|
||||
|
||||
/**
|
||||
* Removes all configured externals.
|
||||
*/
|
||||
public void clear_externals () {
|
||||
externals.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a module as external, leaving it out of the bundle.
|
||||
*
|
||||
* @param external the module specifier to treat as external
|
||||
*/
|
||||
public void add_external (string external) {
|
||||
externals.add (external);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each configured external.
|
||||
*
|
||||
* @param func function called with each external
|
||||
*/
|
||||
public void enumerate_externals (Func<string> func) {
|
||||
foreach (var external in externals)
|
||||
func (external);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Compiler.build}.
|
||||
*/
|
||||
public sealed class BuildOptions : CompilerOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Compiler.watch}.
|
||||
*/
|
||||
public sealed class WatchOptions : CompilerOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* How a compiled bundle's bytes are encoded.
|
||||
*/
|
||||
public enum OutputFormat {
|
||||
/**
|
||||
* Raw, unescaped JavaScript.
|
||||
*/
|
||||
UNESCAPED,
|
||||
/**
|
||||
* A hex-encoded byte string.
|
||||
*/
|
||||
HEX_BYTES,
|
||||
/**
|
||||
* A C string literal.
|
||||
*/
|
||||
C_STRING;
|
||||
|
||||
public static OutputFormat from_nick (string nick) throws Error {
|
||||
@@ -1058,8 +1152,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The module format of a compiled bundle.
|
||||
*/
|
||||
public enum BundleFormat {
|
||||
/**
|
||||
* An ECMAScript module.
|
||||
*/
|
||||
ESM,
|
||||
/**
|
||||
* An immediately-invoked function expression.
|
||||
*/
|
||||
IIFE;
|
||||
|
||||
public static BundleFormat from_nick (string nick) throws Error {
|
||||
@@ -1071,8 +1174,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the compiler type-checks the project.
|
||||
*/
|
||||
public enum TypeCheckMode {
|
||||
/**
|
||||
* Perform full type checking.
|
||||
*/
|
||||
FULL,
|
||||
/**
|
||||
* Skip type checking.
|
||||
*/
|
||||
NONE;
|
||||
|
||||
public static TypeCheckMode from_nick (string nick) throws Error {
|
||||
@@ -1084,8 +1196,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether source maps are included in a compiled bundle.
|
||||
*/
|
||||
public enum SourceMaps {
|
||||
/**
|
||||
* Include source maps.
|
||||
*/
|
||||
INCLUDED,
|
||||
/**
|
||||
* Omit source maps.
|
||||
*/
|
||||
OMITTED;
|
||||
|
||||
public static SourceMaps from_nick (string nick) throws Error {
|
||||
@@ -1097,8 +1218,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which compressor the compiled JavaScript is run through.
|
||||
*/
|
||||
public enum JsCompression {
|
||||
/**
|
||||
* No compression.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Compress with Terser.
|
||||
*/
|
||||
TERSER;
|
||||
|
||||
public static JsCompression from_nick (string nick) throws Error {
|
||||
@@ -1110,9 +1240,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The platform a compiled bundle targets.
|
||||
*/
|
||||
public enum JsPlatform {
|
||||
/**
|
||||
* Frida's Gum runtime inside a target process.
|
||||
*/
|
||||
GUM,
|
||||
/**
|
||||
* A web browser.
|
||||
*/
|
||||
BROWSER,
|
||||
/**
|
||||
* A platform-neutral environment.
|
||||
*/
|
||||
NEUTRAL;
|
||||
|
||||
public static JsPlatform from_nick (string nick) throws Error {
|
||||
|
||||
Generated
+4
-4
@@ -33,7 +33,7 @@
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "^19.5.0",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
@@ -248,9 +248,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "19.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.5.0.tgz",
|
||||
"integrity": "sha512-aka2Jihv/tsmvgffeABBnRueGiz4YILaDAnHsK2EbXS4wIGNxy/OsdzWwIRfvWXwaAju+FKfFFI+q8mG4Jmm1w==",
|
||||
"version": "19.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.10.0.tgz",
|
||||
"integrity": "sha512-GBKAzwmamtNPBXEcIve1yJ6BB9ZgyW9al3+f06S+lDAvennOx9GfFQ9RDY9kP5nJrkiiRHLzgbDFjQ/+g+iw1Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "^19.5.0",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/frida/frida-symbol-replacer
|
||||
|
||||
go 1.24.4
|
||||
go 1.24.3
|
||||
+98
-18
@@ -1,10 +1,20 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Hosts a Frida control endpoint that frida-server-style clients can connect
|
||||
* to, exposing the local system over the network.
|
||||
*/
|
||||
public sealed class ControlService : Object {
|
||||
/**
|
||||
* The endpoint the service listens on.
|
||||
*/
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options the service was configured with.
|
||||
*/
|
||||
public ControlServiceOptions options {
|
||||
get;
|
||||
construct;
|
||||
@@ -39,6 +49,12 @@ namespace Frida {
|
||||
STOPPING
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a control service listening on the given endpoint.
|
||||
*
|
||||
* @param endpoint_params the endpoint to listen on
|
||||
* @param options service options, or null for the defaults
|
||||
*/
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) throws Error {
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
@@ -73,6 +89,14 @@ namespace Frida {
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a control service exposing a specific device rather than the
|
||||
* local system.
|
||||
*
|
||||
* @param device the device to expose
|
||||
* @param endpoint_params the endpoint to listen on
|
||||
* @param options service options, or null for the defaults
|
||||
*/
|
||||
public async ControlService.with_device (Device device, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null, Cancellable? cancellable = null) throws Error, IOError {
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
@@ -98,6 +122,7 @@ namespace Frida {
|
||||
|
||||
private void assign_session (HostSession session, HostSessionProvider provider) {
|
||||
host_session = session;
|
||||
host_session.spawn_gating_disabled.connect (on_spawn_gating_disabled);
|
||||
host_session.spawn_added.connect (notify_spawn_added);
|
||||
host_session.child_added.connect (notify_child_added);
|
||||
host_session.child_removed.connect (notify_child_removed);
|
||||
@@ -111,6 +136,9 @@ namespace Frida {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the service, binding the endpoint and accepting clients.
|
||||
*/
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STOPPED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -146,6 +174,9 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service, disconnecting clients and releasing the endpoint.
|
||||
*/
|
||||
public async void stop (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STARTED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -359,18 +390,30 @@ namespace Frida {
|
||||
public void kick_authentication_channel (AuthenticationChannel channel) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
channel.connection.close.begin (io_cancellable);
|
||||
perform_kick.begin (channel);
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
private async void perform_kick (AuthenticationChannel channel) {
|
||||
try {
|
||||
yield channel.connection.flush (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
try {
|
||||
yield channel.connection.close (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async void teardown_control_channel (ControlChannel channel) {
|
||||
yield parent.teardown_control_channel (channel);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (requester);
|
||||
public async void enable_spawn_gating (HashTable<string, Variant> options, ControlChannel requester)
|
||||
throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (options, requester);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
@@ -508,20 +551,27 @@ namespace Frida {
|
||||
return channels.iterator ();
|
||||
}
|
||||
|
||||
private async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
private async void enable_spawn_gating (HashTable<string, Variant> options, ControlChannel requester)
|
||||
throws GLib.Error {
|
||||
bool is_first = spawn_gaters.is_empty;
|
||||
spawn_gaters.add (requester);
|
||||
foreach (var spawn in pending_spawn.values)
|
||||
spawn.pending_approvers.add (requester);
|
||||
|
||||
if (is_first)
|
||||
yield host_session.enable_spawn_gating (io_cancellable);
|
||||
yield host_session.enable_spawn_gating_with_options (options, io_cancellable);
|
||||
}
|
||||
|
||||
private async void disable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
if (spawn_gaters.remove (requester)) {
|
||||
foreach (uint pid in pending_spawn.keys.to_array ())
|
||||
host_session.resume.begin (pid, io_cancellable);
|
||||
foreach (uint pid in pending_spawn.keys.to_array ()) {
|
||||
PendingSpawn spawn = pending_spawn[pid];
|
||||
var approvers = spawn.pending_approvers;
|
||||
if (approvers.remove (requester) && approvers.is_empty) {
|
||||
forget_pending_spawn (pid, spawn);
|
||||
host_session.resume.begin (pid, io_cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spawn_gaters.is_empty)
|
||||
@@ -546,11 +596,8 @@ namespace Frida {
|
||||
var approvers = spawn.pending_approvers;
|
||||
approvers.remove (requester);
|
||||
if (approvers.is_empty) {
|
||||
pending_spawn.unset (pid);
|
||||
|
||||
forget_pending_spawn (pid, spawn);
|
||||
yield host_session.resume (pid, io_cancellable);
|
||||
|
||||
notify_spawn_removed (spawn.info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,11 +704,29 @@ namespace Frida {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Backend disabled gating and already resumed everything; drop our mirror, tell the gaters.
|
||||
private void on_spawn_gating_disabled (SpawnGatingDisabledReason reason) {
|
||||
foreach (var spawn in pending_spawn.values.to_array ())
|
||||
notify_spawn_removed (spawn.info);
|
||||
pending_spawn.clear ();
|
||||
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_gating_disabled (reason);
|
||||
spawn_gaters.clear ();
|
||||
}
|
||||
|
||||
private void notify_spawn_added (HostSpawnInfo info) {
|
||||
pending_spawn[info.pid] = new PendingSpawn (info.pid, info.identifier, spawn_gaters.iterator ());
|
||||
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_added (info);
|
||||
}
|
||||
|
||||
private void forget_pending_spawn (uint pid, PendingSpawn spawn) {
|
||||
pending_spawn.unset (pid);
|
||||
notify_spawn_removed (spawn.info);
|
||||
}
|
||||
|
||||
private void notify_spawn_removed (HostSpawnInfo info) {
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_removed (info);
|
||||
@@ -697,12 +762,13 @@ namespace Frida {
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
AgentSessionEntry entry;
|
||||
if (agent_sessions.unset (id, out entry)) {
|
||||
ControlChannel? controller = entry.controller;
|
||||
if (controller != null) {
|
||||
controller.agent_sessions.remove (id);
|
||||
controller.agent_session_detached (id, reason, crash);
|
||||
}
|
||||
bool entry_found = agent_sessions.unset (id, out entry);
|
||||
ControlChannel? controller = entry_found ? entry.controller : null;
|
||||
GLib.info ("detach-trace: ControlService session detached id=%s reason=%d entry_found=%s controller=%p",
|
||||
id.handle, reason, entry_found.to_string (), controller);
|
||||
if (controller != null) {
|
||||
controller.agent_sessions.remove (id);
|
||||
controller.agent_session_detached (id, reason, crash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,7 +962,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (this);
|
||||
yield parent.enable_spawn_gating (make_parameters_dict (), this);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (options, this);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error {
|
||||
@@ -1124,13 +1195,22 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a {@link ControlService}.
|
||||
*/
|
||||
public sealed class ControlServiceOptions : Object {
|
||||
/**
|
||||
* Whether to preload the agent for faster first attach.
|
||||
*/
|
||||
public bool enable_preload {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to capture and report crashes in instrumented processes.
|
||||
*/
|
||||
public bool report_crashes {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -65,6 +65,7 @@ namespace Frida {
|
||||
|
||||
construct {
|
||||
helper.output.connect (on_output);
|
||||
helper.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
helper.spawn_added.connect (on_spawn_added);
|
||||
helper.spawn_removed.connect (on_spawn_removed);
|
||||
|
||||
@@ -80,6 +81,7 @@ namespace Frida {
|
||||
|
||||
#if IOS || TVOS
|
||||
fruit_controller = new FruitController (this, io_cancellable);
|
||||
fruit_controller.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
fruit_controller.spawn_added.connect (on_spawn_added);
|
||||
fruit_controller.spawn_removed.connect (on_spawn_removed);
|
||||
fruit_controller.process_crashed.connect (on_process_crashed);
|
||||
@@ -91,6 +93,7 @@ namespace Frida {
|
||||
|
||||
#if IOS || TVOS
|
||||
yield fruit_controller.close (cancellable);
|
||||
fruit_controller.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
fruit_controller.spawn_added.disconnect (on_spawn_added);
|
||||
fruit_controller.spawn_removed.disconnect (on_spawn_removed);
|
||||
fruit_controller.process_crashed.disconnect (on_process_crashed);
|
||||
@@ -113,6 +116,7 @@ namespace Frida {
|
||||
|
||||
yield helper.close (cancellable);
|
||||
helper.output.disconnect (on_output);
|
||||
helper.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
helper.spawn_added.disconnect (on_spawn_added);
|
||||
helper.spawn_removed.disconnect (on_spawn_removed);
|
||||
|
||||
@@ -182,11 +186,17 @@ namespace Frida {
|
||||
return yield process_enumerator.enumerate_processes (ProcessQueryOptions._deserialize (options));
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var scope = SpawnGatingOptions._deserialize (options).scope;
|
||||
#if IOS || TVOS
|
||||
if (scope == PROCESSES)
|
||||
throw new Error.NOT_SUPPORTED (
|
||||
"Process-level gating is unavailable on this OS; use the 'applications' scope");
|
||||
|
||||
yield fruit_controller.enable_spawn_gating (cancellable);
|
||||
#else
|
||||
yield helper.enable_spawn_gating (cancellable);
|
||||
yield helper.enable_spawn_gating (scope, cancellable);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -196,6 +206,8 @@ namespace Frida {
|
||||
#else
|
||||
yield helper.disable_spawn_gating (cancellable);
|
||||
#endif
|
||||
|
||||
spawn_gating_disabled (APPLICATION_REQUESTED);
|
||||
}
|
||||
|
||||
public override async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -218,7 +230,16 @@ namespace Frida {
|
||||
return yield fruit_controller.spawn (path, options, cancellable);
|
||||
#endif
|
||||
|
||||
return yield helper.spawn (path, options, cancellable);
|
||||
UnixInputStream? stdin_stream = null;
|
||||
UnixOutputStream? stdout_stream = null;
|
||||
UnixOutputStream? stderr_stream = null;
|
||||
if (options.stdio == INHERIT) {
|
||||
stdin_stream = new UnixInputStream (0, false);
|
||||
stdout_stream = new UnixOutputStream (1, false);
|
||||
stderr_stream = new UnixOutputStream (2, false);
|
||||
}
|
||||
|
||||
return yield helper.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
protected override async void await_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -278,6 +299,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_spawn_gating_cancelled () {
|
||||
spawn_gating_disabled (WATCHDOG_TIMEOUT);
|
||||
}
|
||||
|
||||
private void on_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -353,6 +378,7 @@ namespace Frida {
|
||||
|
||||
#if IOS || TVOS
|
||||
private sealed class FruitController : Object, MappedAgentContainer {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void process_crashed (CrashInfo crash);
|
||||
@@ -377,6 +403,7 @@ namespace Frida {
|
||||
private bool spawn_gating_enabled = false;
|
||||
private Gee.HashMap<string, Promise<uint>> spawn_requests = new Gee.HashMap<string, Promise<uint>> ();
|
||||
private Gee.HashMap<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private CrashReporterState crash_reporter_state;
|
||||
private Gee.HashMap<uint, ReportCrashAgent> crash_agents = new Gee.HashMap<uint, ReportCrashAgent> ();
|
||||
@@ -415,6 +442,8 @@ namespace Frida {
|
||||
|
||||
helper.process_resumed.connect (on_process_resumed);
|
||||
helper.process_killed.connect (on_process_killed);
|
||||
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
~FruitController () {
|
||||
@@ -448,6 +477,8 @@ namespace Frida {
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
spawn_gating_enabled = false;
|
||||
|
||||
watchdog.clear ();
|
||||
|
||||
yield launchd_agent.disable_spawn_gating (cancellable);
|
||||
|
||||
var pending = pending_spawn.values.to_array ();
|
||||
@@ -512,6 +543,7 @@ namespace Frida {
|
||||
HostSpawnInfo? info;
|
||||
if (!pending_spawn.unset (pid, out info))
|
||||
return false;
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (info);
|
||||
|
||||
yield helper.resume (pid, cancellable);
|
||||
@@ -644,6 +676,11 @@ namespace Frida {
|
||||
launchd_agent.claim_process.begin (pid, io_cancellable);
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
disable_spawn_gating.begin (io_cancellable);
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private async void handle_spawn (HostSpawnInfo info) {
|
||||
try {
|
||||
var pid = info.pid;
|
||||
@@ -662,6 +699,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
pending_spawn[pid] = info;
|
||||
watchdog.arm (pid);
|
||||
spawn_added (info);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <gio/gunixoutputstream.h>
|
||||
#include <glib-unix.h>
|
||||
#include <spawn.h>
|
||||
#include <stdio.h>
|
||||
@@ -793,7 +795,8 @@ _frida_darwin_helper_backend_schedule_on_dispatch_queue (FridaDarwinHelperBacken
|
||||
|
||||
guint
|
||||
_frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar * path,
|
||||
FridaHostSpawnOptions * options, FridaStdioPipes ** pipes, GError ** error)
|
||||
FridaHostSpawnOptions * options, GUnixInputStream * stdin_stream, GUnixOutputStream * stdout_stream,
|
||||
GUnixOutputStream * stderr_stream, FridaStdioPipes ** pipes, GError ** error)
|
||||
{
|
||||
pid_t pid;
|
||||
FridaSpawnInstance * instance;
|
||||
@@ -830,11 +833,17 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
if (stdio_error != NULL)
|
||||
goto propagate_stdio_error;
|
||||
|
||||
if (in_fd != NULL)
|
||||
if (stdin_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_input_stream_get_fd (stdin_stream), 0);
|
||||
else if (in_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, in_fd->handle, 0);
|
||||
if (out_fd != NULL)
|
||||
if (stdout_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_output_stream_get_fd (stdout_stream), 1);
|
||||
else if (out_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, out_fd->handle, 1);
|
||||
if (err_fd != NULL)
|
||||
if (stderr_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_output_stream_get_fd (stderr_stream), 2);
|
||||
else if (err_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, err_fd->handle, 2);
|
||||
|
||||
aslr_value = g_hash_table_lookup (options->aux, "aslr");
|
||||
@@ -2873,7 +2882,9 @@ frida_spawn_instance_on_server_recv (void * context)
|
||||
thread_state_flavor_t state_flavor = GUM_DARWIN_THREAD_STATE_FLAVOR;
|
||||
mach_msg_type_number_t state_count = GUM_DARWIN_THREAD_STATE_COUNT;
|
||||
GumDarwinUnifiedThreadState state;
|
||||
#if __has_feature (ptrauth_calls)
|
||||
gboolean pc_may_need_fixup_due_to_ptrauth_failure;
|
||||
#endif
|
||||
guint i, current_bp_index;
|
||||
FridaBreakpoint * breakpoint = NULL;
|
||||
gboolean carry_on, pc_changed;
|
||||
@@ -2909,8 +2920,6 @@ frida_spawn_instance_on_server_recv (void * context)
|
||||
__darwin_arm_thread_state64_set_pc_fptr (state.ts_64, GSIZE_TO_POINTER (gum_sign_code_address (ret_gadget)));
|
||||
}
|
||||
}
|
||||
#else
|
||||
pc_may_need_fixup_due_to_ptrauth_failure = FALSE;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_I386
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Frida {
|
||||
|
||||
dtrace_agent = DTraceAgent.try_open ();
|
||||
if (dtrace_agent != null) {
|
||||
dtrace_agent.gating_cancelled.connect (on_dtrace_agent_gating_cancelled);
|
||||
dtrace_agent.spawn_added.connect (on_dtrace_agent_spawn_added);
|
||||
dtrace_agent.spawn_removed.connect (on_dtrace_agent_spawn_removed);
|
||||
}
|
||||
@@ -97,6 +98,7 @@ namespace Frida {
|
||||
yield;
|
||||
|
||||
if (dtrace_agent != null) {
|
||||
dtrace_agent.gating_cancelled.disconnect (on_dtrace_agent_gating_cancelled);
|
||||
dtrace_agent.spawn_added.disconnect (on_dtrace_agent_spawn_added);
|
||||
dtrace_agent.spawn_removed.disconnect (on_dtrace_agent_spawn_removed);
|
||||
yield dtrace_agent.close (cancellable);
|
||||
@@ -107,8 +109,8 @@ namespace Frida {
|
||||
public async void preload (Cancellable? cancellable) throws Error, IOError {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
get_dtrace_agent ().enable_spawn_gating ();
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
get_dtrace_agent ().enable_spawn_gating (scope);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -126,12 +128,13 @@ namespace Frida {
|
||||
return dtrace_agent;
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
if (!FileUtils.test (path, EXISTS))
|
||||
throw new Error.EXECUTABLE_NOT_FOUND ("Unable to find executable at '%s'", path);
|
||||
|
||||
StdioPipes? pipes;
|
||||
var child_pid = _spawn (path, options, out pipes);
|
||||
var child_pid = _spawn (path, options, stdin_stream, stdout_stream, stderr_stream, out pipes);
|
||||
|
||||
ChildWatch.add ((Pid) child_pid, on_child_dead);
|
||||
|
||||
@@ -629,6 +632,10 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private void on_dtrace_agent_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_dtrace_agent_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -664,7 +671,8 @@ namespace Frida {
|
||||
protected extern void _destroy_dispatch_context ();
|
||||
protected extern void _schedule_on_dispatch_queue (DispatchWorker worker);
|
||||
|
||||
protected extern uint _spawn (string path, HostSpawnOptions options, out StdioPipes? pipes) throws Error;
|
||||
protected extern uint _spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, out StdioPipes? pipes) throws Error;
|
||||
protected extern static void _launch (string identifier, HostSpawnOptions options, LaunchCompletionHandler on_complete);
|
||||
protected extern static bool _is_suspended (uint task) throws Error;
|
||||
public extern static void resume_process (uint task) throws Error;
|
||||
@@ -688,15 +696,22 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public sealed class DTraceAgent : Object {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
|
||||
private Subprocess? dtrace;
|
||||
private DataInputStream input;
|
||||
private SpawnGatingScope scope;
|
||||
private Gee.HashMap<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
// Present in every path inside an app bundle, so APPLICATIONS gates the whole app
|
||||
// (helpers, XPC services, ...), not just its entry point.
|
||||
private const string APP_BUNDLE_MARKER = ".app/";
|
||||
|
||||
public static DTraceAgent? try_open () {
|
||||
if (Posix.getuid () != 0)
|
||||
return null;
|
||||
@@ -704,6 +719,15 @@ namespace Frida {
|
||||
return new DTraceAgent ();
|
||||
}
|
||||
|
||||
construct {
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
disable_spawn_gating.begin (io_cancellable);
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (dtrace != null) {
|
||||
try {
|
||||
@@ -714,10 +738,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public void enable_spawn_gating () throws Error {
|
||||
public void enable_spawn_gating (SpawnGatingScope scope) throws Error {
|
||||
if (dtrace != null)
|
||||
throw new Error.INVALID_OPERATION ("Already enabled");
|
||||
|
||||
this.scope = scope;
|
||||
|
||||
string? predicate = Environment.get_variable ("FRIDA_DTRACE_PREDICATE");
|
||||
string predicate_clause;
|
||||
if (predicate != null)
|
||||
@@ -776,6 +802,7 @@ namespace Frida {
|
||||
spawn_removed (e.value);
|
||||
}
|
||||
pending_spawn.clear ();
|
||||
watchdog.clear ();
|
||||
}
|
||||
|
||||
public HostSpawnInfo[] enumerate_pending_spawn () {
|
||||
@@ -788,8 +815,10 @@ namespace Frida {
|
||||
|
||||
public void on_resume (uint pid) {
|
||||
HostSpawnInfo? info;
|
||||
if (pending_spawn.unset (pid, out info))
|
||||
if (pending_spawn.unset (pid, out info)) {
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (info);
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
@@ -837,8 +866,14 @@ namespace Frida {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scope != PROCESSES && !path.contains (APP_BUNDLE_MARKER)) {
|
||||
DarwinHelperBackend.resume_without_validation (pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
var info = HostSpawnInfo (pid, path);
|
||||
pending_spawn[pid] = info;
|
||||
watchdog.arm (pid);
|
||||
spawn_added (info);
|
||||
}
|
||||
} catch (IOError e) {
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Frida {
|
||||
public async void preload (Cancellable? cancellable) throws Error, IOError {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ namespace Frida {
|
||||
yield obtain (cancellable);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
var helper = yield obtain (cancellable);
|
||||
try {
|
||||
yield helper.enable_spawn_gating (cancellable);
|
||||
yield helper.enable_spawn_gating (scope, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_helper_error (e);
|
||||
}
|
||||
@@ -83,9 +83,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
var helper = yield obtain (cancellable);
|
||||
try {
|
||||
if (stdin_stream != null)
|
||||
return yield helper.spawn_with_stdio (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
return yield helper.spawn (path, options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_helper_error (e);
|
||||
@@ -340,6 +343,9 @@ namespace Frida {
|
||||
GLib.SpawnFlags flags =
|
||||
GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
|
||||
GLib.SpawnFlags.DO_NOT_REAP_CHILD |
|
||||
GLib.SpawnFlags.STDOUT_TO_DEV_NULL |
|
||||
GLib.SpawnFlags.STDERR_TO_DEV_NULL |
|
||||
/* GLib.SpawnFlags.STDIN_FROM_DEV_NULL */ 2048 |
|
||||
/* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
|
||||
|
||||
GLib.Process.spawn_async (null, argv, null, flags, null, out pending_pid);
|
||||
@@ -420,6 +426,7 @@ namespace Frida {
|
||||
|
||||
proxy = pending_proxy;
|
||||
proxy.output.connect (on_output);
|
||||
proxy.gating_cancelled.connect (on_gating_cancelled);
|
||||
proxy.spawn_added.connect (on_spawn_added);
|
||||
proxy.spawn_removed.connect (on_spawn_removed);
|
||||
proxy.injected.connect (on_injected);
|
||||
@@ -439,6 +446,7 @@ namespace Frida {
|
||||
obtain_request = null;
|
||||
|
||||
proxy.output.disconnect (on_output);
|
||||
proxy.gating_cancelled.disconnect (on_gating_cancelled);
|
||||
proxy.spawn_added.disconnect (on_spawn_added);
|
||||
proxy.spawn_removed.disconnect (on_spawn_removed);
|
||||
proxy.injected.disconnect (on_injected);
|
||||
@@ -460,6 +468,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Frida {
|
||||
construct {
|
||||
backend.idle.connect (on_backend_idle);
|
||||
backend.output.connect (on_backend_output);
|
||||
backend.gating_cancelled.connect (on_backend_gating_cancelled);
|
||||
backend.spawn_added.connect (on_backend_spawn_added);
|
||||
backend.spawn_removed.connect (on_backend_spawn_removed);
|
||||
backend.injected.connect (on_backend_injected);
|
||||
@@ -93,6 +94,7 @@ namespace Frida {
|
||||
}
|
||||
backend.idle.disconnect (on_backend_idle);
|
||||
backend.output.disconnect (on_backend_output);
|
||||
backend.gating_cancelled.disconnect (on_backend_gating_cancelled);
|
||||
backend.spawn_added.disconnect (on_backend_spawn_added);
|
||||
backend.spawn_removed.disconnect (on_backend_spawn_removed);
|
||||
backend.injected.disconnect (on_backend_injected);
|
||||
@@ -147,8 +149,8 @@ namespace Frida {
|
||||
shutdown.begin ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield backend.enable_spawn_gating (cancellable);
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
yield backend.enable_spawn_gating (scope, cancellable);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -160,7 +162,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, cancellable);
|
||||
return yield backend.spawn (path, options, null, null, null, cancellable);
|
||||
}
|
||||
|
||||
public async uint spawn_with_stdio (string path, HostSpawnOptions options, UnixInputStream stdin_stream,
|
||||
UnixOutputStream stdout_stream, UnixOutputStream stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
public async void launch (string identifier, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -237,6 +244,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_backend_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_backend_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace Frida {
|
||||
public interface DarwinHelper : Object {
|
||||
public signal void output (uint pid, int fd, uint8[] data);
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void injected (uint id, uint pid, bool has_mapped_module, DarwinModuleDetails mapped_module);
|
||||
@@ -16,10 +17,11 @@ namespace Frida {
|
||||
|
||||
public abstract async void preload (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void launch (string identifier, HostSpawnOptions options,
|
||||
Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void notify_launch_completed (string identifier, uint pid,
|
||||
@@ -49,6 +51,7 @@ namespace Frida {
|
||||
[DBus (name = "re.frida.Helper")]
|
||||
public interface DarwinRemoteHelper : Object {
|
||||
public signal void output (uint pid, int fd, uint8[] data);
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void injected (uint id, uint pid, bool has_mapped_module, DarwinModuleDetails mapped_module);
|
||||
@@ -58,10 +61,12 @@ namespace Frida {
|
||||
|
||||
public abstract async void stop (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async uint spawn_with_stdio (string path, HostSpawnOptions options, UnixInputStream stdin_stream,
|
||||
UnixOutputStream stdout_stream, UnixOutputStream stderr_stream, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void launch (string identifier, HostSpawnOptions options, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void notify_launch_completed (string identifier, uint pid,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <errno.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
@@ -98,6 +99,7 @@ extern int proc_pidpath (int pid, void * buffer, uint32_t buffer_size);
|
||||
#endif
|
||||
|
||||
static void frida_add_process_metadata (GHashTable * parameters, const struct kinfo_proc * process);
|
||||
static GVariant * frida_query_process_argv (guint pid);
|
||||
|
||||
static struct kinfo_proc * frida_system_query_kinfo_procs (guint * count);
|
||||
static GVariant * frida_uid_to_name (uid_t uid);
|
||||
@@ -614,7 +616,15 @@ frida_collect_process_info_from_kinfo (struct kinfo_proc * process, FridaEnumera
|
||||
info.name = g_path_get_basename (path);
|
||||
|
||||
if (scope != FRIDA_SCOPE_MINIMAL)
|
||||
{
|
||||
GVariant * argv;
|
||||
|
||||
g_hash_table_insert (info.parameters, g_strdup ("path"), g_variant_ref_sink (g_variant_new_string (path)));
|
||||
|
||||
argv = frida_query_process_argv (info.pid);
|
||||
if (argv != NULL)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("argv"), g_variant_ref_sink (argv));
|
||||
}
|
||||
}
|
||||
|
||||
if (still_alive)
|
||||
@@ -822,6 +832,64 @@ frida_add_process_metadata (GHashTable * parameters, const struct kinfo_proc * p
|
||||
g_date_time_unref (t0);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_query_process_argv (guint pid)
|
||||
{
|
||||
GVariant * result = NULL;
|
||||
int argmax;
|
||||
size_t argmax_size = sizeof (argmax);
|
||||
int argmax_mib[] = { CTL_KERN, KERN_ARGMAX };
|
||||
gchar * buffer = NULL;
|
||||
size_t buffer_size;
|
||||
int args_mib[] = { CTL_KERN, KERN_PROCARGS2, (int) pid };
|
||||
int argc;
|
||||
const gchar * cursor, * end;
|
||||
GVariantBuilder builder;
|
||||
int i;
|
||||
|
||||
if (sysctl (argmax_mib, G_N_ELEMENTS (argmax_mib), &argmax, &argmax_size, NULL, 0) != 0)
|
||||
goto beach;
|
||||
|
||||
buffer = g_malloc (argmax);
|
||||
buffer_size = argmax;
|
||||
|
||||
if (sysctl (args_mib, G_N_ELEMENTS (args_mib), buffer, &buffer_size, NULL, 0) != 0)
|
||||
goto beach;
|
||||
|
||||
if (buffer_size < sizeof (int))
|
||||
goto beach;
|
||||
|
||||
memcpy (&argc, buffer, sizeof (int));
|
||||
if (argc <= 0)
|
||||
goto beach;
|
||||
|
||||
cursor = buffer + sizeof (int);
|
||||
end = buffer + buffer_size;
|
||||
|
||||
while (cursor != end && *cursor != '\0')
|
||||
cursor++;
|
||||
while (cursor != end && *cursor == '\0')
|
||||
cursor++;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
|
||||
for (i = 0; i != argc && cursor != end; i++)
|
||||
{
|
||||
gsize arg_length = strnlen (cursor, end - cursor);
|
||||
g_variant_builder_add_value (&builder, g_variant_new_take_string (g_strndup (cursor, arg_length)));
|
||||
cursor += arg_length;
|
||||
if (cursor != end)
|
||||
cursor++;
|
||||
}
|
||||
|
||||
result = g_variant_builder_end (&builder);
|
||||
|
||||
beach:
|
||||
g_free (buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct kinfo_proc *
|
||||
frida_system_query_kinfo_procs (guint * count)
|
||||
{
|
||||
|
||||
@@ -521,6 +521,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
// Forward the error untranslated so the caller can spot UNKNOWN_METHOD from an older remote.
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
var server = yield get_remote_server (cancellable);
|
||||
yield server.session.enable_spawn_gating_with_options (options, cancellable);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
var server = yield get_remote_server (cancellable);
|
||||
try {
|
||||
@@ -920,6 +927,7 @@ namespace Frida {
|
||||
server.connection.on_closed.connect (on_remote_connection_closed);
|
||||
|
||||
var session = server.session;
|
||||
session.spawn_gating_disabled.connect (on_remote_spawn_gating_disabled);
|
||||
session.spawn_added.connect (on_remote_spawn_added);
|
||||
session.spawn_removed.connect (on_remote_spawn_removed);
|
||||
session.child_added.connect (on_remote_child_added);
|
||||
@@ -934,6 +942,7 @@ namespace Frida {
|
||||
server.connection.on_closed.disconnect (on_remote_connection_closed);
|
||||
|
||||
var session = server.session;
|
||||
session.spawn_gating_disabled.disconnect (on_remote_spawn_gating_disabled);
|
||||
session.spawn_added.disconnect (on_remote_spawn_added);
|
||||
session.spawn_removed.disconnect (on_remote_spawn_removed);
|
||||
session.child_added.disconnect (on_remote_child_added);
|
||||
@@ -954,6 +963,10 @@ namespace Frida {
|
||||
on_remote_agent_session_detached (remote_id, CONNECTION_TERMINATED, no_crash);
|
||||
}
|
||||
|
||||
private void on_remote_spawn_gating_disabled (SpawnGatingDisabledReason reason) {
|
||||
spawn_gating_disabled (reason);
|
||||
}
|
||||
|
||||
private void on_remote_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -980,7 +993,11 @@ namespace Frida {
|
||||
|
||||
private void on_remote_agent_session_detached (AgentSessionId remote_id, SessionDetachReason reason, CrashInfo crash) {
|
||||
AgentSessionId? local_id;
|
||||
if (!remote_agent_sessions.unset (remote_id, out local_id))
|
||||
bool mapping_found = remote_agent_sessions.unset (remote_id, out local_id);
|
||||
GLib.info ("detach-trace: Droidy remote session detached remote_id=%s reason=%d mapping_found=%s local_id=%s",
|
||||
remote_id.handle, reason, mapping_found.to_string (),
|
||||
mapping_found ? local_id.handle : "-");
|
||||
if (!mapping_found)
|
||||
return;
|
||||
|
||||
bool agent_session_found = agent_sessions.unset (local_id);
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Watches a filesystem path and reports changes to it.
|
||||
*/
|
||||
public sealed class FileMonitor : Object {
|
||||
/**
|
||||
* Emitted when a change is observed at the watched path.
|
||||
*
|
||||
* @param file_path the path that changed
|
||||
* @param other_file_path the other path involved, for example the
|
||||
* destination of a rename, or null
|
||||
* @param event the kind of change
|
||||
*/
|
||||
public signal void change (string file_path, string? other_file_path, FileMonitorEvent event);
|
||||
|
||||
/**
|
||||
* The path being watched.
|
||||
*/
|
||||
public string path {
|
||||
get;
|
||||
construct;
|
||||
@@ -9,6 +23,11 @@ namespace Frida {
|
||||
|
||||
private GLib.FileMonitor monitor;
|
||||
|
||||
/**
|
||||
* Creates a file monitor for the given path.
|
||||
*
|
||||
* @param path the filesystem path to watch
|
||||
*/
|
||||
public FileMonitor (string path) {
|
||||
Object (path: path);
|
||||
}
|
||||
@@ -17,6 +36,10 @@ namespace Frida {
|
||||
clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts watching, after which {@link FileMonitor.change} is emitted on
|
||||
* each change.
|
||||
*/
|
||||
public async void enable (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (monitor != null)
|
||||
throw new Error.INVALID_OPERATION ("Already enabled");
|
||||
@@ -43,6 +66,9 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops watching.
|
||||
*/
|
||||
public async void disable (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (monitor == null)
|
||||
throw new Error.INVALID_OPERATION ("Already disabled");
|
||||
|
||||
@@ -77,7 +77,8 @@ namespace Frida {
|
||||
return yield process_enumerator.enumerate_processes (ProcessQueryOptions._deserialize (options));
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on this OS");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <errno.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
@@ -23,6 +24,7 @@ static void frida_add_process_metadata (GHashTable * parameters, const struct ki
|
||||
|
||||
static struct kinfo_proc * frida_system_query_kinfo_procs (guint * count);
|
||||
static gboolean frida_system_query_proc_pathname (pid_t pid, gchar * path, gsize size);
|
||||
static GVariant * frida_query_process_argv (guint pid);
|
||||
static GVariant * frida_uid_to_name (uid_t uid);
|
||||
|
||||
void
|
||||
@@ -116,7 +118,15 @@ frida_collect_process_info_from_kinfo (struct kinfo_proc * process, FridaEnumera
|
||||
info.name = g_strdup (process->ki_comm);
|
||||
|
||||
if (scope != FRIDA_SCOPE_MINIMAL)
|
||||
{
|
||||
GVariant * argv;
|
||||
|
||||
g_hash_table_insert (info.parameters, g_strdup ("path"), g_variant_ref_sink (g_variant_new_string (path)));
|
||||
|
||||
argv = frida_query_process_argv (info.pid);
|
||||
if (argv != NULL)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("argv"), g_variant_ref_sink (argv));
|
||||
}
|
||||
}
|
||||
|
||||
if (still_alive)
|
||||
@@ -221,6 +231,50 @@ frida_system_query_proc_pathname (pid_t pid, gchar * path, gsize size)
|
||||
return success;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_query_process_argv (guint pid)
|
||||
{
|
||||
GVariant * result = NULL;
|
||||
int mib[4];
|
||||
gchar * buffer = NULL;
|
||||
size_t size;
|
||||
const gchar * cursor, * end;
|
||||
GVariantBuilder builder;
|
||||
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_ARGS;
|
||||
mib[3] = pid;
|
||||
|
||||
size = 0;
|
||||
if (sysctl (mib, G_N_ELEMENTS (mib), NULL, &size, NULL, 0) != 0 || size == 0)
|
||||
goto beach;
|
||||
|
||||
buffer = g_malloc (size);
|
||||
if (sysctl (mib, G_N_ELEMENTS (mib), buffer, &size, NULL, 0) != 0)
|
||||
goto beach;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
|
||||
cursor = buffer;
|
||||
end = buffer + size;
|
||||
while (cursor != end)
|
||||
{
|
||||
gsize arg_length = strnlen (cursor, end - cursor);
|
||||
g_variant_builder_add_value (&builder, g_variant_new_take_string (g_strndup (cursor, arg_length)));
|
||||
cursor += arg_length;
|
||||
if (cursor != end)
|
||||
cursor++;
|
||||
}
|
||||
|
||||
result = g_variant_builder_end (&builder);
|
||||
|
||||
beach:
|
||||
g_free (buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_uid_to_name (uid_t uid)
|
||||
{
|
||||
|
||||
+899
-6
File diff suppressed because it is too large.
Load diff
@@ -742,6 +742,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
// Forward the error untranslated so the caller can spot UNKNOWN_METHOD from an older remote.
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
var server = yield get_remote_server (cancellable);
|
||||
yield server.session.enable_spawn_gating_with_options (options, cancellable);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
var server = yield get_remote_server (cancellable);
|
||||
try {
|
||||
@@ -1426,6 +1433,7 @@ namespace Frida {
|
||||
server.connection.on_closed.connect (on_remote_connection_closed);
|
||||
|
||||
var session = server.session;
|
||||
session.spawn_gating_disabled.connect (on_remote_spawn_gating_disabled);
|
||||
session.spawn_added.connect (on_remote_spawn_added);
|
||||
session.spawn_removed.connect (on_remote_spawn_removed);
|
||||
session.child_added.connect (on_remote_child_added);
|
||||
@@ -1440,6 +1448,7 @@ namespace Frida {
|
||||
server.connection.on_closed.disconnect (on_remote_connection_closed);
|
||||
|
||||
var session = server.session;
|
||||
session.spawn_gating_disabled.disconnect (on_remote_spawn_gating_disabled);
|
||||
session.spawn_added.disconnect (on_remote_spawn_added);
|
||||
session.spawn_removed.disconnect (on_remote_spawn_removed);
|
||||
session.child_added.disconnect (on_remote_child_added);
|
||||
@@ -1460,6 +1469,10 @@ namespace Frida {
|
||||
on_remote_agent_session_detached (remote_id, CONNECTION_TERMINATED, no_crash);
|
||||
}
|
||||
|
||||
private void on_remote_spawn_gating_disabled (SpawnGatingDisabledReason reason) {
|
||||
spawn_gating_disabled (reason);
|
||||
}
|
||||
|
||||
private void on_remote_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
|
||||
+13
-13
@@ -507,6 +507,19 @@ namespace Frida.Fruity.Injector {
|
||||
yield output.write_all_async (bytes.get_data (), io_priority, cancellable, out bytes_written);
|
||||
}
|
||||
|
||||
foreach (unowned Gum.DarwinSegment segment in module.segments.data) {
|
||||
uint64 address = segment.vm_address + slide;
|
||||
|
||||
var protect_command = lldb.make_buffer_builder ()
|
||||
.append_uint8 (UploadCommandType.PROTECT)
|
||||
.append_uint64 (address)
|
||||
.append_uint32 ((uint32) segment.vm_size)
|
||||
.append_uint32 ((uint32) segment.protection)
|
||||
.build ();
|
||||
yield output.write_all_async (protect_command.get_data (), io_priority, cancellable,
|
||||
out bytes_written);
|
||||
}
|
||||
|
||||
if (!threaded_items.is_empty) {
|
||||
var command = lldb.make_buffer_builder ()
|
||||
.append_uint8 (UploadCommandType.APPLY_THREADED)
|
||||
@@ -538,19 +551,6 @@ namespace Frida.Fruity.Injector {
|
||||
out bytes_written);
|
||||
}
|
||||
|
||||
foreach (unowned Gum.DarwinSegment segment in module.segments.data) {
|
||||
uint64 address = segment.vm_address + slide;
|
||||
|
||||
var protect_command = lldb.make_buffer_builder ()
|
||||
.append_uint8 (UploadCommandType.PROTECT)
|
||||
.append_uint64 (address)
|
||||
.append_uint32 ((uint32) segment.vm_size)
|
||||
.append_uint32 ((uint32) segment.protection)
|
||||
.build ();
|
||||
yield output.write_all_async (protect_command.get_data (), io_priority, cancellable,
|
||||
out bytes_written);
|
||||
}
|
||||
|
||||
var construct_commands = lldb.make_buffer_builder ();
|
||||
module.enumerate_init_pointers (ptrs => {
|
||||
construct_commands
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Frida.LLDB {
|
||||
|
||||
protected override async void enable_extensions (Cancellable? cancellable) throws Error, IOError {
|
||||
yield execute_simple ("QThreadSuffixSupported", cancellable);
|
||||
supported_features.add ("thread-suffix");
|
||||
yield execute_simple ("QListThreadsInStopReply", cancellable);
|
||||
yield execute_simple ("QSetDetachOnError:0", cancellable);
|
||||
}
|
||||
|
||||
+7
-3
@@ -1774,7 +1774,7 @@ namespace Frida.GDB {
|
||||
request
|
||||
.append_c ('p')
|
||||
.append_register_id (reg.id);
|
||||
if ("vcont" in client.features) {
|
||||
if ("thread-suffix" in client.features) {
|
||||
request
|
||||
.append (";thread:")
|
||||
.append (id)
|
||||
@@ -1785,7 +1785,11 @@ namespace Frida.GDB {
|
||||
|
||||
var response = yield client.query (request.build (), cancellable);
|
||||
|
||||
return Protocol.parse_integer_value (response.payload, client.byte_order);
|
||||
unowned string payload = response.payload;
|
||||
if (payload.length == 0 || payload[0] == 'E')
|
||||
throw new Error.NOT_SUPPORTED ("Unable to read register “%s”", name);
|
||||
|
||||
return Protocol.parse_integer_value (payload, client.byte_order);
|
||||
}
|
||||
|
||||
public async void write_register (string name, uint64 val, Cancellable? cancellable = null) throws Error, IOError {
|
||||
@@ -1797,7 +1801,7 @@ namespace Frida.GDB {
|
||||
.append_register_id (reg.id)
|
||||
.append_c ('=')
|
||||
.append (Protocol.unparse_integer_value (val, client.pointer_size, client.byte_order));
|
||||
if ("vcont" in client.features) {
|
||||
if ("thread-suffix" in client.features) {
|
||||
command
|
||||
.append (";thread:")
|
||||
.append (id)
|
||||
|
||||
@@ -484,7 +484,12 @@ namespace Frida {
|
||||
public abstract async HostProcessInfo[] enumerate_processes (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError;
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield enable_spawn_gating_with_options (make_parameters_dict (), cancellable);
|
||||
}
|
||||
|
||||
public abstract async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
@@ -866,6 +871,8 @@ namespace Frida {
|
||||
|
||||
private void on_agent_session_provider_closed (AgentSessionId id) {
|
||||
var closed_after_opening = agent_sessions.unset (id);
|
||||
GLib.info ("detach-trace: BaseDBusHostSession provider closed id=%s closed_after_opening=%s",
|
||||
id.handle, closed_after_opening.to_string ());
|
||||
if (!closed_after_opening)
|
||||
return;
|
||||
var reason = SessionDetachReason.APPLICATION_REQUESTED;
|
||||
@@ -968,7 +975,10 @@ namespace Frida {
|
||||
|
||||
var crash_info = (crash != null) ? crash : CrashInfo.empty ();
|
||||
foreach (var id in entry.sessions) {
|
||||
if (agent_sessions.unset (id))
|
||||
bool still_registered = agent_sessions.unset (id);
|
||||
GLib.info ("detach-trace: BaseDBusHostSession teardown id=%s reason=%d still_registered=%s",
|
||||
id.handle, reason, still_registered.to_string ());
|
||||
if (still_registered)
|
||||
agent_session_detached (id, reason, crash_info);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace Frida {
|
||||
p.close ();
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
if (!FileUtils.test (path, EXISTS))
|
||||
throw new Error.EXECUTABLE_NOT_FOUND ("Unable to find executable at '%s'", path);
|
||||
|
||||
@@ -56,7 +57,11 @@ namespace Frida {
|
||||
if (pid == 0) {
|
||||
Posix.setsid ();
|
||||
|
||||
if (pipes != null) {
|
||||
if (stdin_stream != null) {
|
||||
Posix.dup2 (stdin_stream.get_fd (), 0);
|
||||
Posix.dup2 (stdout_stream.get_fd (), 1);
|
||||
Posix.dup2 (stderr_stream.get_fd (), 2);
|
||||
} else if (pipes != null) {
|
||||
Posix.dup2 (in_fd.handle, 0);
|
||||
Posix.dup2 (out_fd.handle, 1);
|
||||
Posix.dup2 (err_fd.handle, 2);
|
||||
|
||||
@@ -54,9 +54,10 @@ namespace Frida {
|
||||
return _resource_store;
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
var helper = yield obtain_for_path (path, cancellable);
|
||||
return yield helper.spawn (path, options, cancellable);
|
||||
return yield helper.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
public async void prepare_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -379,7 +380,12 @@ namespace Frida {
|
||||
} catch (Error e) {
|
||||
string[] argv = { helper_file.path, socket_address };
|
||||
|
||||
GLib.SpawnFlags flags = GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN | /* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
|
||||
GLib.SpawnFlags flags =
|
||||
GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
|
||||
GLib.SpawnFlags.STDOUT_TO_DEV_NULL |
|
||||
GLib.SpawnFlags.STDERR_TO_DEV_NULL |
|
||||
/* GLib.SpawnFlags.STDIN_FROM_DEV_NULL */ 2048 |
|
||||
/* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
|
||||
GLib.Process.spawn_async (null, argv, envp, flags, null, out pending_pid);
|
||||
}
|
||||
|
||||
@@ -498,8 +504,11 @@ namespace Frida {
|
||||
proxy.uninjected.disconnect (on_uninjected);
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
if (stdin_stream != null)
|
||||
return yield proxy.spawn_with_stdio (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
return yield proxy.spawn (path, options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
|
||||
@@ -121,7 +121,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, cancellable);
|
||||
return yield backend.spawn (path, options, null, null, null, cancellable);
|
||||
}
|
||||
|
||||
public async uint spawn_with_stdio (string path, HostSpawnOptions options, UnixInputStream stdin_stream,
|
||||
UnixOutputStream stdout_stream, UnixOutputStream stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
public async void prepare_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
|
||||
@@ -5,7 +5,8 @@ namespace Frida {
|
||||
|
||||
public abstract async void close (Cancellable? cancellable) throws IOError;
|
||||
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void prepare_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void await_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void cancel_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError;
|
||||
@@ -50,6 +51,8 @@ namespace Frida {
|
||||
public abstract async void stop (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async uint spawn_with_stdio (string path, HostSpawnOptions options, UnixInputStream stdin_stream,
|
||||
UnixOutputStream stdout_stream, UnixOutputStream stderr_stream, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void prepare_exec_transition (uint pid, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void await_exec_transition (uint pid, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void cancel_exec_transition (uint pid, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -53,6 +53,7 @@ static void frida_enable_close_on_exec (int fd, const FridaLibcApi * libc);
|
||||
|
||||
static size_t frida_strlen (const char * str);
|
||||
|
||||
static pid_t frida_getpid (void);
|
||||
static pid_t frida_gettid (void);
|
||||
|
||||
__attribute__ ((section (".text.entrypoint")))
|
||||
@@ -111,7 +112,14 @@ frida_main (void * user_data)
|
||||
if (agent_codefd == -1)
|
||||
goto beach;
|
||||
|
||||
libc->sprintf (agent_path, "/proc/self/fd/%d", agent_codefd);
|
||||
/*
|
||||
* Spell out our PID instead of using /proc/self so the path resolves the same
|
||||
* from outside the process. An attached debugger handles the dynamic linker's
|
||||
* library-load event by reading the new object from this path; with /proc/self
|
||||
* that read hits the debugger's own fd table and fails, wedging the thread mid
|
||||
* dlopen (), so the agent never finishes loading.
|
||||
*/
|
||||
libc->sprintf (agent_path, "/proc/%d/fd/%d", frida_getpid (), agent_codefd);
|
||||
|
||||
ctx->agent_handle = libc->dlopen (agent_path, libc->dlopen_flags, pretend_caller_addr);
|
||||
if (ctx->agent_handle == NULL)
|
||||
@@ -400,6 +408,12 @@ frida_strlen (const char * str)
|
||||
return n;
|
||||
}
|
||||
|
||||
static pid_t
|
||||
frida_getpid (void)
|
||||
{
|
||||
return frida_syscall_0 (SYS_getpid);
|
||||
}
|
||||
|
||||
static pid_t
|
||||
frida_gettid (void)
|
||||
{
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Frida {
|
||||
|
||||
#if ANDROID
|
||||
robo_launcher = new RoboLauncher (this, io_cancellable);
|
||||
robo_launcher.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
robo_launcher.spawn_added.connect (on_spawn_added);
|
||||
robo_launcher.spawn_removed.connect (on_spawn_removed);
|
||||
|
||||
@@ -97,6 +98,7 @@ namespace Frida {
|
||||
public override async void close (Cancellable? cancellable) throws IOError {
|
||||
#if ANDROID
|
||||
yield robo_launcher.close (cancellable);
|
||||
robo_launcher.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
robo_launcher.spawn_added.disconnect (on_spawn_added);
|
||||
robo_launcher.spawn_removed.disconnect (on_spawn_removed);
|
||||
|
||||
@@ -110,6 +112,7 @@ namespace Frida {
|
||||
#endif
|
||||
|
||||
if (spawn_gater != null) {
|
||||
spawn_gater.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
spawn_gater.spawn_added.disconnect (on_spawn_added);
|
||||
spawn_gater.spawn_removed.disconnect (on_spawn_removed);
|
||||
spawn_gater.stop ();
|
||||
@@ -276,25 +279,28 @@ namespace Frida {
|
||||
#endif
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var helper_process = helper as LinuxHelperProcess;
|
||||
if (helper_process != null)
|
||||
yield helper_process.preload (cancellable);
|
||||
|
||||
var gater = ensure_spawn_gater ();
|
||||
if (gater.state == STOPPED) {
|
||||
#if ANDROID
|
||||
try {
|
||||
// RoboLauncher gates app launches; only add the system-wide eBPF gater for PROCESSES.
|
||||
// Let a start failure propagate — the caller opted in, so don't silently downgrade.
|
||||
if (SpawnGatingOptions._deserialize (options).scope == PROCESSES) {
|
||||
var gater = ensure_spawn_gater ();
|
||||
if (gater.state == STOPPED)
|
||||
gater.start ();
|
||||
} catch (Error e) {
|
||||
}
|
||||
#else
|
||||
gater.start ();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
yield robo_launcher.enable_spawn_gating (cancellable);
|
||||
#else
|
||||
// No portable way to tell a GUI app from any other execve here, so both scopes map to
|
||||
// the same system-wide gater.
|
||||
var gater = ensure_spawn_gater ();
|
||||
if (gater.state == STOPPED)
|
||||
gater.start ();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -304,11 +310,14 @@ namespace Frida {
|
||||
#if ANDROID
|
||||
yield robo_launcher.disable_spawn_gating (cancellable);
|
||||
#endif
|
||||
|
||||
spawn_gating_disabled (APPLICATION_REQUESTED);
|
||||
}
|
||||
|
||||
private SpawnGater ensure_spawn_gater () {
|
||||
if (spawn_gater == null) {
|
||||
spawn_gater = new SpawnGater (helper);
|
||||
spawn_gater.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
spawn_gater.spawn_added.connect (on_spawn_added);
|
||||
spawn_gater.spawn_removed.connect (on_spawn_removed);
|
||||
}
|
||||
@@ -338,7 +347,16 @@ namespace Frida {
|
||||
return yield robo_launcher.spawn (program, options, cancellable);
|
||||
#endif
|
||||
|
||||
return yield helper.spawn (program, options, cancellable);
|
||||
UnixInputStream? stdin_stream = null;
|
||||
UnixOutputStream? stdout_stream = null;
|
||||
UnixOutputStream? stderr_stream = null;
|
||||
if (options.stdio == INHERIT) {
|
||||
stdin_stream = new UnixInputStream (0, false);
|
||||
stdout_stream = new UnixOutputStream (1, false);
|
||||
stderr_stream = new UnixOutputStream (2, false);
|
||||
}
|
||||
|
||||
return yield helper.spawn (program, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
protected override async void prepare_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -506,6 +524,10 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
private void on_spawn_gating_cancelled () {
|
||||
spawn_gating_disabled (WATCHDOG_TIMEOUT);
|
||||
}
|
||||
|
||||
private void on_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -1344,6 +1366,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private sealed class RoboLauncher : Object {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
|
||||
@@ -1367,6 +1390,7 @@ namespace Frida {
|
||||
private bool spawn_gating_enabled = false;
|
||||
private Gee.HashMap<string, Promise<uint>> spawn_requests = new Gee.HashMap<string, Promise<uint>> ();
|
||||
private Gee.HashMap<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private delegate void CompletionNotify (GLib.Error? error);
|
||||
|
||||
@@ -1379,11 +1403,22 @@ namespace Frida {
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
disable_spawn_gating.begin (io_cancellable);
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
public async void preload (Cancellable? cancellable) throws Error, IOError {
|
||||
yield ensure_loaded (cancellable);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
watchdog.clear ();
|
||||
|
||||
if (ensure_request != null) {
|
||||
try {
|
||||
yield ensure_loaded (cancellable);
|
||||
@@ -1422,6 +1457,8 @@ namespace Frida {
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
spawn_gating_enabled = false;
|
||||
|
||||
watchdog.clear ();
|
||||
|
||||
var pending = pending_spawn.values.to_array ();
|
||||
pending_spawn.clear ();
|
||||
foreach (var spawn in pending) {
|
||||
@@ -1508,8 +1545,10 @@ namespace Frida {
|
||||
connection.resume.begin (io_cancellable);
|
||||
|
||||
HostSpawnInfo? info;
|
||||
if (pending_spawn.unset (pid, out info))
|
||||
if (pending_spawn.unset (pid, out info)) {
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1598,10 +1637,21 @@ namespace Frida {
|
||||
|
||||
on_complete (null);
|
||||
} catch (GLib.Error e) {
|
||||
if (is_transient_usap_patch_failure (name, e)) {
|
||||
on_complete (null);
|
||||
return;
|
||||
}
|
||||
|
||||
on_complete (e);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool is_transient_usap_patch_failure (string process_name, GLib.Error error) {
|
||||
return process_name.has_prefix ("usap") &&
|
||||
error is Error.NOT_SUPPORTED &&
|
||||
error.message == "Unable to locate android.os.Process.setArgV0() slot; please file a bug";
|
||||
}
|
||||
|
||||
private async void inject_zymbiote (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
var prep = yield prepare_zymbiote_injection (pid, cancellable);
|
||||
|
||||
@@ -1709,15 +1759,14 @@ namespace Frida {
|
||||
|
||||
foreach (var m in maps) {
|
||||
unowned string path = m.path;
|
||||
if (path.has_suffix ("/libstagefright.so") && m.readable && m.executable) {
|
||||
if (payload_base == 0) {
|
||||
payload_base = m.end - Gum.query_page_size ();
|
||||
payload_original_protection = Posix.PROT_READ | Posix.PROT_EXEC;
|
||||
if (m.writable)
|
||||
payload_original_protection |= Posix.PROT_WRITE;
|
||||
payload_path = path;
|
||||
payload_file_offset = m.file_offset;
|
||||
}
|
||||
if (payload_base == 0 && m.executable &&
|
||||
(path.has_suffix ("/libstagefright.so") || path.has_suffix ("/libmedia.so"))) {
|
||||
payload_base = m.end - Gum.query_page_size ();
|
||||
payload_original_protection = Posix.PROT_READ | Posix.PROT_EXEC;
|
||||
if (m.writable)
|
||||
payload_original_protection |= Posix.PROT_WRITE;
|
||||
payload_path = path;
|
||||
payload_file_offset = m.file_offset;
|
||||
} else if (path.has_suffix ("/libc.so")) {
|
||||
if (libc_path == null)
|
||||
libc_path = path;
|
||||
@@ -2080,6 +2129,7 @@ namespace Frida {
|
||||
} else if (spawn_gating_enabled) {
|
||||
var spawn_info = HostSpawnInfo (hello.pid, hello.process_name);
|
||||
pending_spawn[hello.pid] = spawn_info;
|
||||
watchdog.arm (hello.pid);
|
||||
spawn_added (spawn_info);
|
||||
needs_resume = true;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Frida {
|
||||
private const uint REGION_POLL_INTERVAL_MS = 5;
|
||||
private const uint DRAIN_MS = 50;
|
||||
private const uint SAMPLE_WINDOW_MS = 250;
|
||||
private const uint STACK_SCAN_DEPTH = 8;
|
||||
private const uint MIN_TRIGGER_VOTES = 4;
|
||||
private const uint64 MIN_TRIGGER_BYTES = 192;
|
||||
#if X86 || X86_64
|
||||
private const int SCAN_STEP = 1;
|
||||
#else
|
||||
@@ -100,7 +100,8 @@ namespace Frida {
|
||||
if (managed) {
|
||||
Posix.kill ((Posix.pid_t) pid, Posix.Signal.USR1);
|
||||
} else {
|
||||
uint64 alt = yield discover_trigger (maps, target, cancellable);
|
||||
uint64 alt = yield discover_trigger (maps, region, rendezvous, mmap_impl, target,
|
||||
cancellable);
|
||||
if (alt != 0 && mem.read_pointer (rendezvous.mmap_result) == 0) {
|
||||
yield restore_trigger (target, original);
|
||||
target = alt;
|
||||
@@ -135,7 +136,12 @@ namespace Frida {
|
||||
release_callers (target);
|
||||
}
|
||||
|
||||
// Tear down symmetrically with install_bootstrap: re-block the entry so callers spin
|
||||
// there while we put the body back, and only then restore the first instruction. A
|
||||
// multi-byte prologue (e.g. endbr64) would otherwise be re-entered mid-restore, with
|
||||
// the fall-through still routing into a half-written instruction.
|
||||
private async void restore_trigger (uint64 target, uint8[] original) throws Error, IOError {
|
||||
block_callers (target);
|
||||
yield sleep_ms (DRAIN_MS);
|
||||
mem.write_memory (target + BOOTSTRAP_OFFSET, original[BOOTSTRAP_OFFSET:original.length]);
|
||||
restore_prologue (target, original);
|
||||
@@ -622,28 +628,105 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
// Sample what the target runs and pick a libc function to re-hook. Returns 0 when
|
||||
// perf is unavailable or nothing usable turns up.
|
||||
private async uint64 discover_trigger (ProcMapsSnapshot maps, uint64 exclude, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
// Sample what the target runs and pick a libc function to re-hook. Prefer eBPF
|
||||
// stack sampling, falling back to /proc when it is unavailable — typically
|
||||
// because perf/eBPF demands privileges we lack. Returns 0 when nothing usable
|
||||
// turns up.
|
||||
private async uint64 discover_trigger (ProcMapsSnapshot maps, RegionLayout region, StackRendezvous rendezvous,
|
||||
uint64 mmap_impl, uint64 exclude, Cancellable? cancellable) throws Error, IOError {
|
||||
Gee.List<SampledStack> stacks;
|
||||
|
||||
var sampler = new ActivitySampler (pid);
|
||||
try {
|
||||
sampler.start ();
|
||||
yield sleep_ms (SAMPLE_WINDOW_MS);
|
||||
sampler.stop ();
|
||||
stacks = sampler.stacks;
|
||||
} catch (Error e) {
|
||||
return 0;
|
||||
stacks = yield sample_threads_via_proc (maps, cancellable);
|
||||
}
|
||||
yield sleep_ms (SAMPLE_WINDOW_MS);
|
||||
sampler.stop ();
|
||||
|
||||
return hottest_libc_function (maps, sampler.stacks, exclude);
|
||||
return hottest_libc_function (maps, region, rendezvous, mmap_impl, stacks, exclude);
|
||||
}
|
||||
|
||||
// Each sample's leaf is the function the target is actually running; an unwind
|
||||
// past it is unreliable for the frame-less assembly the hot mem*/str* routines
|
||||
// are. Resolve the leaf to its enclosing function and re-hook the hottest one big
|
||||
// enough to hold the stub.
|
||||
private uint64 hottest_libc_function (ProcMapsSnapshot maps, Gee.List<SampledStack> stacks, uint64 exclude)
|
||||
throws Error {
|
||||
private async Gee.List<SampledStack> sample_threads_via_proc (ProcMapsSnapshot maps, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var stacks = new Gee.ArrayList<SampledStack> ();
|
||||
|
||||
var timer = new Timer ();
|
||||
while (timer.elapsed () * 1000.0 < SAMPLE_WINDOW_MS) {
|
||||
cancellable.set_error_if_cancelled ();
|
||||
|
||||
foreach (uint tid in enumerate_thread_ids ()) {
|
||||
uint64[] frames = read_thread_frames (maps, tid);
|
||||
if (frames.length != 0)
|
||||
stacks.add (new SampledStack (frames));
|
||||
}
|
||||
|
||||
yield sleep_ms (REGION_POLL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
return stacks;
|
||||
}
|
||||
|
||||
private Gee.List<uint> enumerate_thread_ids () {
|
||||
var tids = new Gee.ArrayList<uint> ();
|
||||
try {
|
||||
var dir = Dir.open ("/proc/%u/task".printf (pid));
|
||||
string? name;
|
||||
while ((name = dir.read_name ()) != null) {
|
||||
uint tid;
|
||||
if (uint.try_parse (name, out tid))
|
||||
tids.add (tid);
|
||||
}
|
||||
} catch (FileError e) {
|
||||
}
|
||||
return tids;
|
||||
}
|
||||
|
||||
// The last two fields of /proc/<pid>/task/<tid>/syscall are the thread's user-space
|
||||
// stack pointer and PC. The PC lands in whatever libc routine issued the syscall;
|
||||
// once the target is multi-threaded that is a tiny arch wrapper too small to host
|
||||
// the stub, so also hand back a few stack words, where the caller's return address
|
||||
// points at a larger routine we can re-hook instead.
|
||||
private uint64[] read_thread_frames (ProcMapsSnapshot maps, uint tid) throws Error {
|
||||
string contents;
|
||||
try {
|
||||
FileUtils.get_contents ("/proc/%u/task/%u/syscall".printf (pid, tid), out contents);
|
||||
} catch (FileError e) {
|
||||
return {};
|
||||
}
|
||||
|
||||
string[] fields = contents.strip ().split (" ");
|
||||
if (fields.length < 2)
|
||||
return {};
|
||||
|
||||
uint64 pc;
|
||||
if (!uint64.try_parse (fields[fields.length - 1], out pc, null, 0) || pc == 0)
|
||||
return {};
|
||||
|
||||
uint64 sp;
|
||||
var stack = (uint64.try_parse (fields[fields.length - 2], out sp, null, 0) && sp != 0)
|
||||
? maps.find_mapping (sp)
|
||||
: null;
|
||||
if (stack == null)
|
||||
return new uint64[] { pc };
|
||||
|
||||
uint depth = uint.min (STACK_SCAN_DEPTH, (uint) ((stack.end - sp) / 8));
|
||||
var frames = new uint64[1 + depth];
|
||||
frames[0] = pc;
|
||||
for (uint i = 0; i != depth; i++)
|
||||
frames[1 + i] = mem.read_pointer (sp + (uint64) i * 8);
|
||||
return frames;
|
||||
}
|
||||
|
||||
// Walk each sample from its leaf outward and vote for the first frame that lands in
|
||||
// a libc function big enough to hold the stub, then re-hook the hottest. Preferring
|
||||
// the leaf keeps us on the function actually running; falling through to the caller
|
||||
// rescues the cases where the leaf is a too-small wrapper (e.g. the syscall
|
||||
// cancellation arch shim a multi-threaded target parks in).
|
||||
private uint64 hottest_libc_function (ProcMapsSnapshot maps, RegionLayout region, StackRendezvous rendezvous,
|
||||
uint64 mmap_impl, Gee.List<SampledStack> stacks, uint64 exclude) throws Error {
|
||||
unowned Gum.Module libc = Gum.Process.get_libc_module ();
|
||||
var remote = maps.find_module_by_path (libc.path);
|
||||
var local = ProcMapsSnapshot.from_pid (Posix.getpid ()).find_module_by_path (libc.path);
|
||||
@@ -653,18 +736,25 @@ namespace Frida {
|
||||
var symbols = function_symbols (libc, local.start, remote.start);
|
||||
var exports = function_exports (libc, local.start, remote.start);
|
||||
|
||||
uint64 min_bytes = trigger_stub_footprint (remote.start, region, rendezvous, mmap_impl);
|
||||
|
||||
var votes = new Gee.HashMap<uint64?, uint> (Numeric.uint64_hash, Numeric.uint64_equal);
|
||||
foreach (var sample in stacks) {
|
||||
if (sample.frames.length == 0)
|
||||
continue;
|
||||
uint64 leaf = sample.frames[0];
|
||||
var m = maps.find_mapping (leaf);
|
||||
if (m == null || m.path != libc.path)
|
||||
continue;
|
||||
uint64 entry = resolve_function (leaf, symbols, exports);
|
||||
if (entry == 0 || entry == exclude)
|
||||
continue;
|
||||
votes[entry] = votes[entry] + 1;
|
||||
for (int depth = 0; depth != sample.frames.length; depth++) {
|
||||
uint64 frame = sample.frames[depth];
|
||||
var m = maps.find_mapping (frame);
|
||||
if (m == null || m.path != libc.path)
|
||||
continue;
|
||||
// The leaf is the live PC; deeper frames are raw stack words, so only
|
||||
// trust those that a call actually returns to rather than stray data.
|
||||
if (depth != 0 && !is_return_site (frame))
|
||||
continue;
|
||||
uint64 entry = resolve_function (frame, symbols, exports, min_bytes);
|
||||
if (entry == 0 || entry == exclude)
|
||||
continue;
|
||||
votes[entry] = votes[entry] + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64 best = 0;
|
||||
@@ -707,7 +797,7 @@ namespace Frida {
|
||||
// contains it, then a sized export, then — as a last resort — a scan out to the
|
||||
// nearest function boundary either side, which both finds the entry and sizes it.
|
||||
private uint64 resolve_function (uint64 leaf, Gee.List<FunctionExtent> symbols,
|
||||
Gee.List<FunctionExtent> exports) throws Error {
|
||||
Gee.List<FunctionExtent> exports, uint64 min_bytes) throws Error {
|
||||
var fn = containing_function (symbols, leaf);
|
||||
if (fn == null)
|
||||
fn = containing_function (exports, leaf);
|
||||
@@ -715,7 +805,30 @@ namespace Frida {
|
||||
fn = scan_function_extent (leaf);
|
||||
if (fn == null)
|
||||
return 0;
|
||||
return (fn.size >= MIN_TRIGGER_BYTES) ? fn.entry : 0;
|
||||
return (fn.size >= min_bytes) ? fn.entry : 0;
|
||||
}
|
||||
|
||||
// A real return address has a call right before it; data that merely happens to
|
||||
// point into the text does not, so this keeps stray stack words from being re-hooked.
|
||||
private bool is_return_site (uint64 address) throws Error {
|
||||
#if X86 || X86_64
|
||||
return mem.read_memory (address - 5, 1)[0] == 0xe8;
|
||||
#elif ARM64
|
||||
return (peek_u32 (mem.read_memory (address - 4, 4)) & 0xfc000000) == 0x94000000;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// The trigger is overwritten in place, so a candidate is only usable if it is at
|
||||
// least as large as the bootstrap stub we would stamp into it. Build that stub for
|
||||
// the first-injection case (no region to reuse, the larger of the two paths) and
|
||||
// measure it, rather than guessing a fixed floor.
|
||||
private uint64 trigger_stub_footprint (uint64 sample_target, RegionLayout region, StackRendezvous rendezvous,
|
||||
uint64 mmap_impl) throws Error {
|
||||
uint8[] stub = build_trigger_stub (sample_target, rendezvous.cas, mmap_impl, 0, region.total,
|
||||
region.entry_offset);
|
||||
return BOOTSTRAP_OFFSET + stub.length;
|
||||
}
|
||||
|
||||
private FunctionExtent? containing_function (Gee.List<FunctionExtent> functions, uint64 ip) {
|
||||
@@ -845,7 +958,7 @@ namespace Frida {
|
||||
} catch (IOError e) {
|
||||
cancellable.set_error_if_cancelled ();
|
||||
throw new Error.PROCESS_NOT_RESPONDING (
|
||||
"Timed out waiting for the target to trigger the injected stub");
|
||||
"Timed out waiting for the injected agent to connect back");
|
||||
} finally {
|
||||
cancel_source.destroy ();
|
||||
timeout_source.destroy ();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Frida {
|
||||
public sealed class SpawnGater : Object {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace Frida {
|
||||
private State _state = STOPPED;
|
||||
|
||||
private Gee.Map<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private BpfRingbufReader? events_reader;
|
||||
private IOChannel? events_channel;
|
||||
@@ -32,16 +34,30 @@ namespace Frida {
|
||||
private const size_t RINGBUF_SIZE = 1U << 22;
|
||||
private const size_t MAX_FILENAME = 256;
|
||||
|
||||
// bpf_send_signal(SIGSTOP) races our SIGCONT: one that lands first is dropped, so re-send
|
||||
// until the task is observed running.
|
||||
private const uint RESUME_MAX_ATTEMPTS = 20;
|
||||
private const uint RESUME_RETRY_MSEC = 25;
|
||||
|
||||
public SpawnGater (LinuxHelper helper) {
|
||||
Object (helper: helper);
|
||||
}
|
||||
|
||||
construct {
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
protected override void dispose () {
|
||||
stop ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
stop ();
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
public void start () throws Error {
|
||||
assert (state == STOPPED);
|
||||
|
||||
@@ -86,6 +102,7 @@ namespace Frida {
|
||||
perform_resume.begin (spawn.pid);
|
||||
}
|
||||
pending_spawn.clear ();
|
||||
watchdog.clear ();
|
||||
|
||||
events_channel = null;
|
||||
events_reader = null;
|
||||
@@ -105,6 +122,7 @@ namespace Frida {
|
||||
HostSpawnInfo? spawn;
|
||||
if (!pending_spawn.unset (pid, out spawn))
|
||||
return false;
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (spawn);
|
||||
perform_resume.begin (pid);
|
||||
return true;
|
||||
@@ -115,10 +133,37 @@ namespace Frida {
|
||||
yield helper.resume (pid, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is Error.INVALID_ARGUMENT)
|
||||
Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
|
||||
yield resume_external (pid);
|
||||
}
|
||||
}
|
||||
|
||||
private async void resume_external (uint pid) {
|
||||
for (uint attempt = 0; attempt != RESUME_MAX_ATTEMPTS; attempt++) {
|
||||
if (Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT) != 0)
|
||||
return;
|
||||
if (!process_is_stopped (pid))
|
||||
return;
|
||||
|
||||
var source = new TimeoutSource (RESUME_RETRY_MSEC);
|
||||
source.set_callback (resume_external.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool process_is_stopped (uint pid) {
|
||||
string contents;
|
||||
try {
|
||||
FileUtils.get_contents ("/proc/%u/stat".printf (pid), out contents);
|
||||
} catch (FileError e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// State is the char two past the final ')' (comm can itself contain ')').
|
||||
char state = contents[contents.last_index_of_char (')') + 2];
|
||||
return state == 'T' || state == 't';
|
||||
}
|
||||
|
||||
private void process_pending_events () {
|
||||
events_reader.drain (payload => {
|
||||
assert (payload.length == sizeof (ExecveEvent));
|
||||
@@ -130,6 +175,7 @@ namespace Frida {
|
||||
private void handle_event (ExecveEvent * e) {
|
||||
var info = HostSpawnInfo (e->pid, (string) e->command);
|
||||
pending_spawn[e->pid] = info;
|
||||
watchdog.arm (e->pid);
|
||||
spawn_added (info);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ struct _FridaEnumerateProcessesOperation
|
||||
};
|
||||
|
||||
static void frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op);
|
||||
static GVariant * frida_parse_cmdline_argv (const gchar * cmdline_data, gsize cmdline_length);
|
||||
static gboolean frida_is_directory_noexec (const gchar * directory);
|
||||
static gchar * frida_get_application_directory (void);
|
||||
static gboolean frida_add_process_metadata (GHashTable * parameters, const gchar * proc_entry_name);
|
||||
@@ -86,6 +87,8 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
gchar * program_path = NULL;
|
||||
gchar * cmdline_path = NULL;
|
||||
gchar * cmdline_data = NULL;
|
||||
gsize cmdline_length = 0;
|
||||
GVariant * argv = NULL;
|
||||
gchar * name = NULL;
|
||||
|
||||
proc_name = g_strdup_printf ("%u", pid);
|
||||
@@ -100,10 +103,12 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
|
||||
cmdline_path = g_build_filename ("/proc", proc_name, "cmdline", NULL);
|
||||
|
||||
g_file_get_contents (cmdline_path, &cmdline_data, NULL, NULL);
|
||||
g_file_get_contents (cmdline_path, &cmdline_data, &cmdline_length, NULL);
|
||||
if (cmdline_data == NULL)
|
||||
goto beach;
|
||||
|
||||
argv = frida_parse_cmdline_argv (cmdline_data, cmdline_length);
|
||||
|
||||
if (g_str_has_prefix (cmdline_data, "/proc/"))
|
||||
{
|
||||
name = g_path_get_basename (program_path);
|
||||
@@ -129,6 +134,9 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("path"),
|
||||
g_variant_ref_sink (g_variant_new_take_string (g_steal_pointer (&program_path))));
|
||||
|
||||
if (argv != NULL)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("argv"), g_variant_ref_sink (g_steal_pointer (&argv)));
|
||||
|
||||
still_alive = frida_add_process_metadata (info.parameters, proc_name);
|
||||
}
|
||||
|
||||
@@ -138,6 +146,7 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
frida_host_process_info_destroy (&info);
|
||||
|
||||
beach:
|
||||
g_clear_pointer (&argv, g_variant_unref);
|
||||
g_free (name);
|
||||
g_free (cmdline_data);
|
||||
g_free (cmdline_path);
|
||||
@@ -146,6 +155,31 @@ beach:
|
||||
g_free (proc_name);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_parse_cmdline_argv (const gchar * cmdline_data, gsize cmdline_length)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
const gchar * cursor, * end;
|
||||
|
||||
if (cmdline_length == 0)
|
||||
return NULL;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
|
||||
cursor = cmdline_data;
|
||||
end = cmdline_data + cmdline_length;
|
||||
while (cursor != end)
|
||||
{
|
||||
gsize arg_length = strnlen (cursor, end - cursor);
|
||||
g_variant_builder_add_value (&builder, g_variant_new_take_string (g_strndup (cursor, arg_length)));
|
||||
cursor += arg_length;
|
||||
if (cursor != end)
|
||||
cursor++;
|
||||
}
|
||||
|
||||
return g_variant_builder_end (&builder);
|
||||
}
|
||||
|
||||
void
|
||||
frida_system_kill (guint pid)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Searches for and installs npm packages of Frida agents, for use with the
|
||||
* {@link Compiler}.
|
||||
*/
|
||||
public sealed class PackageManager : Object {
|
||||
/**
|
||||
* Emitted as an install progresses.
|
||||
*
|
||||
* @param phase the current phase of the installation
|
||||
* @param fraction progress within the phase, from 0 to 1
|
||||
* @param details optional human-readable detail
|
||||
*/
|
||||
public signal void install_progress (PackageInstallPhase phase, double fraction, string? details = null);
|
||||
|
||||
/**
|
||||
* The npm registry host to use.
|
||||
*/
|
||||
public string registry {
|
||||
get;
|
||||
set;
|
||||
@@ -10,6 +24,9 @@ namespace Frida {
|
||||
|
||||
private Soup.Session session;
|
||||
|
||||
/**
|
||||
* Creates a package manager.
|
||||
*/
|
||||
public PackageManager () {
|
||||
string cache_dir = Path.build_filename (Environment.get_user_cache_dir (), "frida", "package-manager");
|
||||
var cache = new Soup.Cache (cache_dir, Soup.CacheType.SINGLE_USER);
|
||||
@@ -18,6 +35,13 @@ namespace Frida {
|
||||
session.add_feature (cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the registry for packages matching a query.
|
||||
*
|
||||
* @param query the search query
|
||||
* @param options search options such as paging, or null
|
||||
* @return the matching packages
|
||||
*/
|
||||
public async PackageSearchResult search (string query, PackageSearchOptions? options = null,
|
||||
Cancellable? cancellable = null) throws Error, IOError {
|
||||
var opts = (options != null) ? options : new PackageSearchOptions ();
|
||||
@@ -89,6 +113,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs packages into a project, emitting {@link
|
||||
* PackageManager.install_progress} as it goes.
|
||||
*
|
||||
* @param options install options, such as the project directory and the
|
||||
* packages to install, or null
|
||||
* @return the result of the installation
|
||||
*/
|
||||
public async PackageInstallResult install (PackageInstallOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
install_progress (INITIALIZING, 0.0);
|
||||
@@ -2321,34 +2353,80 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The phases an install passes through, reported via
|
||||
* {@link PackageManager.install_progress}.
|
||||
*/
|
||||
public enum PackageInstallPhase {
|
||||
/**
|
||||
* Getting ready to install.
|
||||
*/
|
||||
INITIALIZING,
|
||||
/**
|
||||
* Preparing the dependency set.
|
||||
*/
|
||||
PREPARING_DEPENDENCIES,
|
||||
/**
|
||||
* Resolving a package to a concrete version.
|
||||
*/
|
||||
RESOLVING_PACKAGE,
|
||||
/**
|
||||
* Fetching a resource.
|
||||
*/
|
||||
FETCHING_RESOURCE,
|
||||
/**
|
||||
* The package was already installed.
|
||||
*/
|
||||
PACKAGE_ALREADY_INSTALLED,
|
||||
/**
|
||||
* Downloading a package.
|
||||
*/
|
||||
DOWNLOADING_PACKAGE,
|
||||
/**
|
||||
* A package was installed.
|
||||
*/
|
||||
PACKAGE_INSTALLED,
|
||||
/**
|
||||
* Resolving and installing the full dependency set.
|
||||
*/
|
||||
RESOLVING_AND_INSTALLING_ALL,
|
||||
/**
|
||||
* The installation is complete.
|
||||
*/
|
||||
COMPLETE,
|
||||
}
|
||||
|
||||
/**
|
||||
* A package available in the registry.
|
||||
*/
|
||||
public sealed class Package : Object {
|
||||
/**
|
||||
* The package name.
|
||||
*/
|
||||
public string name {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The package version.
|
||||
*/
|
||||
public string version {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* A short description of the package, if any.
|
||||
*/
|
||||
public string? description {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* A URL with more information about the package, if any.
|
||||
*/
|
||||
public string? url {
|
||||
get;
|
||||
construct;
|
||||
@@ -2372,13 +2450,31 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The role a package plays among a project's dependencies.
|
||||
*/
|
||||
public enum PackageRole {
|
||||
/**
|
||||
* A regular runtime dependency.
|
||||
*/
|
||||
RUNTIME,
|
||||
/**
|
||||
* A development-only dependency.
|
||||
*/
|
||||
DEVELOPMENT,
|
||||
/**
|
||||
* An optional dependency.
|
||||
*/
|
||||
OPTIONAL,
|
||||
/**
|
||||
* A peer dependency.
|
||||
*/
|
||||
PEER
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable list of {@link Package} objects.
|
||||
*/
|
||||
public sealed class PackageList : Object {
|
||||
private Gee.List<Package> items;
|
||||
|
||||
@@ -2386,22 +2482,42 @@ namespace Frida {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of packages in the list.
|
||||
*
|
||||
* @return the count
|
||||
*/
|
||||
public int size () {
|
||||
return items.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the package at the given position.
|
||||
*
|
||||
* @param index zero-based position
|
||||
* @return the package
|
||||
*/
|
||||
public new Package get (int index) {
|
||||
return items.get (index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link PackageManager.search}.
|
||||
*/
|
||||
public class PackageSearchOptions : Object {
|
||||
/**
|
||||
* The number of results to skip, for paging.
|
||||
*/
|
||||
public uint offset {
|
||||
get;
|
||||
set;
|
||||
default = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of results to return.
|
||||
*/
|
||||
public uint limit {
|
||||
get;
|
||||
set;
|
||||
@@ -2409,12 +2525,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a {@link PackageManager.search}.
|
||||
*/
|
||||
public sealed class PackageSearchResult : Object {
|
||||
/**
|
||||
* The packages on this page of results.
|
||||
*/
|
||||
public PackageList packages {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total number of matching packages, across all pages.
|
||||
*/
|
||||
public uint total {
|
||||
get;
|
||||
construct;
|
||||
@@ -2425,39 +2550,71 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link PackageManager.install}.
|
||||
*/
|
||||
public class PackageInstallOptions : Object {
|
||||
internal Gee.List<string> specs = new Gee.ArrayList<string> ();
|
||||
internal Gee.Set<PackageRole> omits = new Gee.HashSet<PackageRole> ();
|
||||
|
||||
/**
|
||||
* The project directory to install into, or null for the current
|
||||
* directory.
|
||||
*/
|
||||
public string? project_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* The role to record the installed packages under.
|
||||
*/
|
||||
public PackageRole role {
|
||||
get;
|
||||
set;
|
||||
default = RUNTIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all package specs to install.
|
||||
*/
|
||||
public void clear_specs () {
|
||||
specs.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a package spec to install, such as `foo` or `foo@1.2.3`.
|
||||
*
|
||||
* @param spec the package spec
|
||||
*/
|
||||
public void add_spec (string spec) {
|
||||
specs.add (spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all omitted roles.
|
||||
*/
|
||||
public void clear_omits () {
|
||||
omits.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Omits dependencies of the given role from the installation.
|
||||
*
|
||||
* @param role the role to omit
|
||||
*/
|
||||
public void add_omit (PackageRole role) {
|
||||
omits.add (role);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a {@link PackageManager.install}.
|
||||
*/
|
||||
public sealed class PackageInstallResult : Object {
|
||||
/**
|
||||
* The packages that were installed.
|
||||
*/
|
||||
public PackageList packages {
|
||||
get;
|
||||
construct;
|
||||
|
||||
+155
-3
@@ -1,17 +1,80 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Hosts a Frida portal: a rendezvous point where instrumented processes
|
||||
* (nodes) and controllers meet, letting controllers reach nodes without a
|
||||
* direct connection to them.
|
||||
*/
|
||||
public sealed class PortalService : Object {
|
||||
/**
|
||||
* Emitted when a node connects to the cluster endpoint.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param remote_address the node's network address
|
||||
*/
|
||||
public signal void node_connected (uint connection_id, SocketAddress remote_address);
|
||||
/**
|
||||
* Emitted when a node joins the portal, identifying its application.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param application the node's application
|
||||
*/
|
||||
public signal void node_joined (uint connection_id, Application application);
|
||||
/**
|
||||
* Emitted when a node leaves the portal.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param application the node's application
|
||||
*/
|
||||
public signal void node_left (uint connection_id, Application application);
|
||||
/**
|
||||
* Emitted when a node disconnects from the cluster endpoint.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param remote_address the node's network address
|
||||
*/
|
||||
public signal void node_disconnected (uint connection_id, SocketAddress remote_address);
|
||||
|
||||
/**
|
||||
* Emitted when a controller connects to the control endpoint.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param remote_address the controller's network address
|
||||
*/
|
||||
public signal void controller_connected (uint connection_id, SocketAddress remote_address);
|
||||
/**
|
||||
* Emitted when a controller disconnects from the control endpoint.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param remote_address the controller's network address
|
||||
*/
|
||||
public signal void controller_disconnected (uint connection_id, SocketAddress remote_address);
|
||||
|
||||
/**
|
||||
* Emitted when a connection successfully authenticates.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param session_info JSON describing the authenticated session
|
||||
*/
|
||||
public signal void authenticated (uint connection_id, string session_info);
|
||||
/**
|
||||
* Emitted when a controller subscribes to portal messages.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
*/
|
||||
public signal void subscribe (uint connection_id);
|
||||
/**
|
||||
* Emitted when a message is received from a connection.
|
||||
*
|
||||
* @param connection_id the connection's ID
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data accompanying the message, if any
|
||||
*/
|
||||
public signal void message (uint connection_id, string json, Bytes? data);
|
||||
|
||||
/**
|
||||
* A device representing the portal itself, for interacting with the
|
||||
* connected nodes.
|
||||
*/
|
||||
public Device device {
|
||||
get {
|
||||
return _device;
|
||||
@@ -19,11 +82,17 @@ namespace Frida {
|
||||
}
|
||||
private Device _device;
|
||||
|
||||
/**
|
||||
* The endpoint that nodes connect to.
|
||||
*/
|
||||
public EndpointParameters cluster_params {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint that controllers connect to, if any.
|
||||
*/
|
||||
public EndpointParameters? control_params {
|
||||
get;
|
||||
construct;
|
||||
@@ -56,6 +125,13 @@ namespace Frida {
|
||||
STARTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a portal service.
|
||||
*
|
||||
* @param cluster_params the endpoint that nodes connect to
|
||||
* @param control_params the endpoint that controllers connect to, or
|
||||
* null to disable the control endpoint
|
||||
*/
|
||||
public PortalService (EndpointParameters cluster_params, EndpointParameters? control_params = null) {
|
||||
Object (cluster_params: cluster_params, control_params: control_params);
|
||||
}
|
||||
@@ -86,12 +162,22 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another endpoint for nodes to connect to.
|
||||
*
|
||||
* @param endpoint_params the endpoint to add
|
||||
*/
|
||||
public void add_cluster_endpoint (EndpointParameters endpoint_params) throws Error {
|
||||
if (state != STOPPED)
|
||||
throw new Error.INVALID_OPERATION ("Cannot add endpoint after service has started");
|
||||
cluster_services.add (make_cluster_service (endpoint_params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another endpoint for controllers to connect to.
|
||||
*
|
||||
* @param endpoint_params the endpoint to add
|
||||
*/
|
||||
public void add_control_endpoint (EndpointParameters endpoint_params) throws Error {
|
||||
if (state != STOPPED)
|
||||
throw new Error.INVALID_OPERATION ("Cannot add endpoint after service has started");
|
||||
@@ -110,6 +196,9 @@ namespace Frida {
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the portal, binding its endpoints and accepting connections.
|
||||
*/
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STOPPED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -143,6 +232,9 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the portal, disconnecting everyone and releasing its endpoints.
|
||||
*/
|
||||
public async void stop (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STARTED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -173,6 +265,11 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects a connection from the portal.
|
||||
*
|
||||
* @param connection_id the connection to disconnect
|
||||
*/
|
||||
public void kick (uint connection_id) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -194,6 +291,13 @@ namespace Frida {
|
||||
entry.connection.close.begin (io_cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a message to a single connection.
|
||||
*
|
||||
* @param connection_id the connection to send to
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data to accompany the message, if any
|
||||
*/
|
||||
public void post (uint connection_id, string json, Bytes? data = null) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -214,6 +318,13 @@ namespace Frida {
|
||||
entry.post (json, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a message to every connection bearing the given tag.
|
||||
*
|
||||
* @param tag the tag identifying the recipients
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data to accompany the message, if any
|
||||
*/
|
||||
public void narrowcast (string tag, string json, Bytes? data = null) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -233,6 +344,12 @@ namespace Frida {
|
||||
entry.post (json, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a message to every connection.
|
||||
*
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data to accompany the message, if any
|
||||
*/
|
||||
public void broadcast (string json, Bytes? data = null) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -264,6 +381,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tags currently applied to a connection.
|
||||
*
|
||||
* @param connection_id the connection to inspect
|
||||
* @return the connection's tags, or null if it has none
|
||||
*/
|
||||
public string[]? enumerate_tags (uint connection_id) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -311,6 +434,13 @@ namespace Frida {
|
||||
return strv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a tag to a connection, so it can be reached with
|
||||
* {@link PortalService.narrowcast}.
|
||||
*
|
||||
* @param connection_id the connection to tag
|
||||
* @param tag the tag to apply
|
||||
*/
|
||||
public void tag (uint connection_id, string tag) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -337,6 +467,12 @@ namespace Frida {
|
||||
entry.tags.add (tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tag from a connection.
|
||||
*
|
||||
* @param connection_id the connection to untag
|
||||
* @param tag the tag to remove
|
||||
*/
|
||||
public void untag (uint connection_id, string tag) {
|
||||
MainContext context = get_main_context ();
|
||||
if (context.is_owner ()) {
|
||||
@@ -473,12 +609,23 @@ namespace Frida {
|
||||
private void kick_authentication_channel (AuthenticationChannel channel) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
channel.connection.close.begin (io_cancellable);
|
||||
perform_kick.begin (channel);
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
private async void perform_kick (AuthenticationChannel channel) {
|
||||
try {
|
||||
yield channel.connection.flush (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
try {
|
||||
yield channel.connection.close (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private async Peer setup_authorized_peer (uint connection_id, DBusConnection connection,
|
||||
EndpointParameters parameters) throws GLib.Error {
|
||||
Peer peer;
|
||||
@@ -651,7 +798,7 @@ namespace Frida {
|
||||
return filtered_apps;
|
||||
}
|
||||
|
||||
private void enable_spawn_gating (ControlChannel requester) {
|
||||
private void enable_spawn_gating (HashTable<string, Variant> options, ControlChannel requester) {
|
||||
spawn_gaters.add (requester);
|
||||
foreach (PendingSpawn spawn in pending_spawn.values) {
|
||||
bool requester_has_access = find_node_accessible_by (requester, spawn.info.pid) != null;
|
||||
@@ -1273,7 +1420,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
parent.enable_spawn_gating (this);
|
||||
parent.enable_spawn_gating (make_parameters_dict (), this);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
parent.enable_spawn_gating (options, this);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace Frida {
|
||||
return yield process_enumerator.enumerate_processes (ProcessQueryOptions._deserialize (options));
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on this OS");
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,11 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
@@ -312,7 +312,10 @@ frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjection
|
||||
|
||||
gum_init ();
|
||||
|
||||
code = gum_alloc_n_pages (1, GUM_PAGE_RWX); /* Executable so debugger can be used to inspect code */
|
||||
page_size = gum_query_page_size ();
|
||||
|
||||
/* Executable so debugger can be used to inspect code */
|
||||
code = gum_memory_allocate (NULL, page_size, page_size, GUM_PAGE_RWX);
|
||||
code_size = frida_remote_worker_context_emit_payload (rwc, code);
|
||||
|
||||
memset (rwc, 0, sizeof (FridaRemoteWorkerContext));
|
||||
@@ -327,7 +330,6 @@ frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjection
|
||||
StringCbCopyA (rwc->entrypoint_name, sizeof (rwc->entrypoint_name), details->entrypoint_name);
|
||||
StringCbCopyA (rwc->entrypoint_data, sizeof (rwc->entrypoint_data), details->entrypoint_data);
|
||||
|
||||
page_size = gum_query_page_size ();
|
||||
g_assert (code_size <= page_size);
|
||||
|
||||
alloc_size = page_size + sizeof (FridaRemoteWorkerContext);
|
||||
@@ -345,7 +347,8 @@ frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjection
|
||||
if (!VirtualProtectEx (details->process_handle, rwc->entrypoint, page_size, PAGE_EXECUTE_READ, &old_protect))
|
||||
goto virtual_protect_ex_failed;
|
||||
|
||||
gum_free_pages (code);
|
||||
gum_memory_free (code, page_size);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
@@ -387,7 +390,8 @@ virtual_protect_ex_failed:
|
||||
error_common:
|
||||
{
|
||||
frida_remote_worker_context_destroy (rwc, details);
|
||||
gum_free_pages (code);
|
||||
gum_memory_free (code, page_size);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,21 @@ namespace Frida {
|
||||
assert_not_reached ();
|
||||
}
|
||||
#else
|
||||
native_helper_path = Frida.helper_path;
|
||||
string native_arch;
|
||||
switch (Gum.Windows.query_native_cpu_type ()) {
|
||||
case IA32:
|
||||
native_arch = "x86";
|
||||
break;
|
||||
case AMD64:
|
||||
native_arch = "x86_64";
|
||||
break;
|
||||
case ARM64:
|
||||
native_arch = "arm64";
|
||||
break;
|
||||
default:
|
||||
assert_not_reached ();
|
||||
}
|
||||
native_helper_path = PathTemplate (Frida.helper_path).expand (native_arch);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,12 @@ frida_helper_manager_stop_services (void * context)
|
||||
frida_service_context_free (self);
|
||||
}
|
||||
|
||||
char *
|
||||
frida_helper_service_make_svcname_base (void)
|
||||
{
|
||||
return g_strdup_printf ("frida-%lu-", GetCurrentProcessId ());
|
||||
}
|
||||
|
||||
char *
|
||||
frida_helper_service_derive_basename (void)
|
||||
{
|
||||
@@ -138,17 +144,17 @@ char *
|
||||
frida_helper_service_derive_filename_for_suffix (const char * suffix)
|
||||
{
|
||||
WCHAR filename_utf16[MAX_PATH + 1] = { 0, };
|
||||
gchar * name, * tail, * tmp;
|
||||
glong len;
|
||||
gchar * name, * native_arch, * tmp;
|
||||
|
||||
GetModuleFileNameW (NULL, filename_utf16, MAX_PATH);
|
||||
|
||||
name = g_utf16_to_utf8 (filename_utf16, -1, NULL, &len, NULL);
|
||||
tail = strrchr (name, '-');
|
||||
if (tail != NULL)
|
||||
name = g_utf16_to_utf8 (filename_utf16, -1, NULL, NULL, NULL);
|
||||
|
||||
native_arch = g_strrstr (name, FRIDA_HELPER_SERVICE_ARCH);
|
||||
if (native_arch != NULL)
|
||||
{
|
||||
tail[1] = '\0';
|
||||
tmp = g_strconcat (name, suffix, ".exe", NULL);
|
||||
*native_arch = '\0';
|
||||
tmp = g_strconcat (name, suffix, native_arch + strlen (FRIDA_HELPER_SERVICE_ARCH), NULL);
|
||||
g_free (name);
|
||||
name = tmp;
|
||||
}
|
||||
@@ -172,18 +178,6 @@ frida_helper_service_derive_svcname_for_self (void)
|
||||
return name;
|
||||
}
|
||||
|
||||
char *
|
||||
frida_helper_service_derive_svcname_for_suffix (const char * suffix)
|
||||
{
|
||||
gchar * basename, * name;
|
||||
|
||||
basename = frida_helper_service_derive_basename ();
|
||||
name = g_strconcat (basename, suffix, NULL);
|
||||
g_free (basename);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
void
|
||||
frida_managed_helper_service_enter_dispatcher_and_main_loop (void)
|
||||
{
|
||||
@@ -449,8 +443,9 @@ frida_register_service (FridaServiceContext * self, const gchar * suffix)
|
||||
WCHAR * servicename;
|
||||
gchar * displayname_utf8;
|
||||
WCHAR * displayname;
|
||||
gchar * filename_utf8;
|
||||
WCHAR * filename;
|
||||
gchar * appname_utf8;
|
||||
gchar * cmdline_utf8;
|
||||
WCHAR * cmdline;
|
||||
|
||||
servicename_utf8 = g_strconcat (self->service_basename, suffix, NULL);
|
||||
servicename = g_utf8_to_utf16 (servicename_utf8, -1, NULL, NULL, NULL);
|
||||
@@ -458,8 +453,9 @@ frida_register_service (FridaServiceContext * self, const gchar * suffix)
|
||||
displayname_utf8 = g_strdup_printf ("Frida %s helper (%s)", suffix, servicename_utf8);
|
||||
displayname = g_utf8_to_utf16 (displayname_utf8, -1, NULL, NULL, NULL);
|
||||
|
||||
filename_utf8 = frida_helper_service_derive_filename_for_suffix (suffix);
|
||||
filename = g_utf8_to_utf16 (filename_utf8, -1, NULL, NULL, NULL);
|
||||
appname_utf8 = frida_helper_service_derive_filename_for_suffix (suffix);
|
||||
cmdline_utf8 = g_strconcat ("\"", appname_utf8, "\" SERVICE \"", servicename_utf8, "\"", NULL);
|
||||
cmdline = g_utf8_to_utf16 (cmdline_utf8, -1, NULL, NULL, NULL);
|
||||
|
||||
handle = CreateServiceW (self->scm,
|
||||
servicename,
|
||||
@@ -468,15 +464,16 @@ frida_register_service (FridaServiceContext * self, const gchar * suffix)
|
||||
SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR_NORMAL,
|
||||
filename,
|
||||
cmdline,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
g_free (filename);
|
||||
g_free (filename_utf8);
|
||||
g_free (cmdline);
|
||||
g_free (cmdline_utf8);
|
||||
g_free (appname_utf8);
|
||||
|
||||
g_free (displayname);
|
||||
g_free (displayname_utf8);
|
||||
@@ -635,17 +632,17 @@ frida_spawn_standalone_service (FridaServiceContext * self, const gchar * suffix
|
||||
HANDLE handle = NULL;
|
||||
gchar * appname_utf8;
|
||||
WCHAR * appname;
|
||||
gchar * svcname_utf8;
|
||||
gchar * cmdline_utf8;
|
||||
WCHAR * cmdline;
|
||||
STARTUPINFOW si = { 0, };
|
||||
PROCESS_INFORMATION pi = { 0, };
|
||||
|
||||
(void) self;
|
||||
|
||||
appname_utf8 = frida_helper_service_derive_filename_for_suffix (suffix);
|
||||
appname = (WCHAR *) g_utf8_to_utf16 (appname_utf8, -1, NULL, NULL, NULL);
|
||||
|
||||
cmdline_utf8 = g_strconcat ("\"", appname_utf8, "\" STANDALONE", NULL);
|
||||
svcname_utf8 = g_strconcat (self->service_basename, suffix, NULL);
|
||||
cmdline_utf8 = g_strconcat ("\"", appname_utf8, "\" STANDALONE \"", svcname_utf8, "\"", NULL);
|
||||
cmdline = (WCHAR *) g_utf8_to_utf16 (cmdline_utf8, -1, NULL, NULL, NULL);
|
||||
|
||||
si.cb = sizeof (si);
|
||||
@@ -658,6 +655,7 @@ frida_spawn_standalone_service (FridaServiceContext * self, const gchar * suffix
|
||||
|
||||
g_free (cmdline);
|
||||
g_free (cmdline_utf8);
|
||||
g_free (svcname_utf8);
|
||||
|
||||
g_free (appname);
|
||||
g_free (appname_utf8);
|
||||
|
||||
@@ -9,10 +9,10 @@ G_GNUC_INTERNAL void * frida_helper_manager_start_services (const char * service
|
||||
FridaPrivilegeLevel level);
|
||||
G_GNUC_INTERNAL void frida_helper_manager_stop_services (void * context);
|
||||
|
||||
G_GNUC_INTERNAL char * frida_helper_service_make_svcname_base (void);
|
||||
G_GNUC_INTERNAL char * frida_helper_service_derive_basename (void);
|
||||
G_GNUC_INTERNAL char * frida_helper_service_derive_filename_for_suffix (const char * suffix);
|
||||
G_GNUC_INTERNAL char * frida_helper_service_derive_svcname_for_self (void);
|
||||
G_GNUC_INTERNAL char * frida_helper_service_derive_svcname_for_suffix (const char * suffix);
|
||||
|
||||
G_GNUC_INTERNAL void frida_managed_helper_service_enter_dispatcher_and_main_loop (void);
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ namespace Frida {
|
||||
return manager.run ();
|
||||
}
|
||||
|
||||
string? svcname = (args.length > 2) ? args[2] : null;
|
||||
|
||||
HelperService service;
|
||||
if (mode == HelperMode.STANDALONE)
|
||||
service = new StandaloneHelperService ();
|
||||
service = new StandaloneHelperService (svcname);
|
||||
else
|
||||
service = new ManagedHelperService ();
|
||||
service = new ManagedHelperService (svcname);
|
||||
service.run ();
|
||||
|
||||
return 0;
|
||||
@@ -104,12 +106,14 @@ namespace Frida {
|
||||
archs.add ("x86_64");
|
||||
archs.add ("x86");
|
||||
|
||||
string basename = HelperService.make_svcname_base ();
|
||||
|
||||
foreach (string arch in archs) {
|
||||
var helper = new ServiceConnection (HelperService.derive_svcname_for_suffix (arch));
|
||||
var helper = new ServiceConnection (basename + arch);
|
||||
helpers.add (helper);
|
||||
}
|
||||
|
||||
context = start_services (HelperService.derive_basename (), archs.to_array (), level);
|
||||
context = start_services (basename, archs.to_array (), level);
|
||||
|
||||
foreach (var helper in helpers) {
|
||||
yield helper.open ();
|
||||
@@ -219,6 +223,11 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public string? svcname {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DBusConnection connection;
|
||||
private uint registration_id;
|
||||
|
||||
@@ -240,7 +249,7 @@ namespace Frida {
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
var stream_request = Pipe.open ("pipe:role=client,name=" + derive_svcname_for_self (), null);
|
||||
var stream_request = Pipe.open ("pipe:role=client,name=" + (svcname ?? derive_svcname_for_self ()), null);
|
||||
var stream = yield stream_request.wait_async (null);
|
||||
|
||||
connection = yield new DBusConnection (stream, null, DELAY_MESSAGE_PROCESSING);
|
||||
@@ -297,16 +306,16 @@ namespace Frida {
|
||||
uninjected (id);
|
||||
}
|
||||
|
||||
public extern static string make_svcname_base ();
|
||||
public extern static string derive_basename ();
|
||||
public extern static string derive_svcname_for_self ();
|
||||
public extern static string derive_svcname_for_suffix (string suffix);
|
||||
}
|
||||
|
||||
private sealed class StandaloneHelperService : HelperService {
|
||||
private MainLoop loop;
|
||||
|
||||
public StandaloneHelperService () {
|
||||
Object (level: PrivilegeLevel.NORMAL);
|
||||
public StandaloneHelperService (string? svcname) {
|
||||
Object (level: PrivilegeLevel.NORMAL, svcname: svcname);
|
||||
}
|
||||
|
||||
public override void run () {
|
||||
@@ -323,8 +332,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private sealed class ManagedHelperService : HelperService {
|
||||
public ManagedHelperService () {
|
||||
Object (level: PrivilegeLevel.ELEVATED);
|
||||
public ManagedHelperService (string? svcname) {
|
||||
Object (level: PrivilegeLevel.ELEVATED, svcname: svcname);
|
||||
}
|
||||
|
||||
public override void run () {
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include "icon-helpers.h"
|
||||
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#define DRIVE_STRINGS_MAX_LENGTH (512)
|
||||
|
||||
@@ -22,6 +24,7 @@ static void frida_collect_process_info (guint pid, FridaEnumerateProcessesOperat
|
||||
static gboolean frida_add_process_metadata (GHashTable * parameters, guint pid, HANDLE process, FridaEnumerateProcessesOperation * op);
|
||||
|
||||
static gboolean frida_get_process_filename (HANDLE process, WCHAR * name, DWORD name_capacity);
|
||||
static GVariant * frida_query_process_argv (guint pid);
|
||||
static GVariant * frida_get_process_user (HANDLE process);
|
||||
static GVariant * frida_get_process_start_time (HANDLE process);
|
||||
|
||||
@@ -117,6 +120,8 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
|
||||
if (op->scope != FRIDA_SCOPE_MINIMAL)
|
||||
{
|
||||
GVariant * argv;
|
||||
|
||||
g_hash_table_insert (info.parameters, g_strdup ("path"),
|
||||
g_variant_ref_sink (g_variant_new_take_string (g_steal_pointer (&program_path))));
|
||||
|
||||
@@ -124,6 +129,10 @@ frida_collect_process_info (guint pid, FridaEnumerateProcessesOperation * op)
|
||||
|
||||
if (pid == op->frontmost_pid)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("frontmost"), g_variant_ref_sink (g_variant_new_boolean (TRUE)));
|
||||
|
||||
argv = frida_query_process_argv (pid);
|
||||
if (argv != NULL)
|
||||
g_hash_table_insert (info.parameters, g_strdup ("argv"), g_variant_ref_sink (argv));
|
||||
}
|
||||
|
||||
if (op->scope == FRIDA_SCOPE_FULL)
|
||||
@@ -254,6 +263,61 @@ frida_get_process_filename (HANDLE process, WCHAR * name, DWORD name_capacity)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_query_process_argv (guint pid)
|
||||
{
|
||||
GVariant * result = NULL;
|
||||
HANDLE process;
|
||||
HMODULE ntdll;
|
||||
NTSTATUS (NTAPI * query_information_process) (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
|
||||
PROCESS_BASIC_INFORMATION basic_info;
|
||||
PEB peb;
|
||||
RTL_USER_PROCESS_PARAMETERS parameters;
|
||||
WCHAR * command_line = NULL;
|
||||
WCHAR ** argv = NULL;
|
||||
int argc, i;
|
||||
GVariantBuilder builder;
|
||||
|
||||
process = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
||||
if (process == NULL)
|
||||
goto beach;
|
||||
|
||||
ntdll = GetModuleHandleW (L"ntdll.dll");
|
||||
query_information_process = (gpointer) GetProcAddress (ntdll, "NtQueryInformationProcess");
|
||||
|
||||
if (query_information_process (process, ProcessBasicInformation, &basic_info, sizeof (basic_info), NULL) != 0)
|
||||
goto beach;
|
||||
|
||||
if (!ReadProcessMemory (process, basic_info.PebBaseAddress, &peb, sizeof (peb), NULL))
|
||||
goto beach;
|
||||
|
||||
if (!ReadProcessMemory (process, peb.ProcessParameters, ¶meters, sizeof (parameters), NULL))
|
||||
goto beach;
|
||||
|
||||
command_line = g_malloc (parameters.CommandLine.Length + sizeof (WCHAR));
|
||||
if (!ReadProcessMemory (process, parameters.CommandLine.Buffer, command_line, parameters.CommandLine.Length, NULL))
|
||||
goto beach;
|
||||
command_line[parameters.CommandLine.Length / sizeof (WCHAR)] = L'\0';
|
||||
|
||||
argv = CommandLineToArgvW (command_line, &argc);
|
||||
if (argv == NULL)
|
||||
goto beach;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
for (i = 0; i != argc; i++)
|
||||
g_variant_builder_add_value (&builder, g_variant_new_take_string (g_utf16_to_utf8 (argv[i], -1, NULL, NULL, NULL)));
|
||||
result = g_variant_builder_end (&builder);
|
||||
|
||||
beach:
|
||||
if (argv != NULL)
|
||||
LocalFree (argv);
|
||||
g_free (command_line);
|
||||
if (process != NULL)
|
||||
CloseHandle (process);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
frida_get_process_user (HANDLE process)
|
||||
{
|
||||
|
||||
@@ -164,7 +164,8 @@ namespace Frida {
|
||||
return yield process_enumerator.enumerate_processes (ProcessQueryOptions._deserialize (options));
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on this OS");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/frida-gum.git
|
||||
revision = 17.11.0
|
||||
revision = 17.16.4
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/glib.git
|
||||
revision = 90a8b77b3d4cdf0482eb398fb2000dcceaeb15f0
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
dependency_names = glib-2.0, gobject-2.0, gthread-2.0, gmodule-2.0, gio-2.0, gio-unix-2.0, gio-win32-2.0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
test_sources = [
|
||||
'test-system.vala',
|
||||
'test-gdb.vala',
|
||||
'test-host-session.vala',
|
||||
'runner.vala',
|
||||
'runner-glue.c',
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace Frida.Test {
|
||||
|
||||
Frida.SystemTest.add_tests ();
|
||||
|
||||
Frida.GDBTest.add_tests ();
|
||||
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
Frida.InjectorTest.add_tests ();
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
namespace Frida.GDBTest {
|
||||
public static void add_tests () {
|
||||
GLib.Test.add_func ("/GDB/register-access-uses-thread-suffix", () => {
|
||||
var h = new Harness ((h) => register_access_uses_thread_suffix.begin (h as Harness));
|
||||
h.run ();
|
||||
});
|
||||
}
|
||||
|
||||
private static async void register_access_uses_thread_suffix (Harness h) {
|
||||
FakeStub? stub = null;
|
||||
try {
|
||||
stub = yield FakeStub.open ();
|
||||
|
||||
var client = yield ThreadSuffixClient.open (stub.client_stream);
|
||||
|
||||
var thread = client.exception.thread;
|
||||
var x0 = yield thread.read_register ("x0");
|
||||
assert_true (x0 == 0);
|
||||
|
||||
yield client.close ();
|
||||
} catch (GLib.Error e) {
|
||||
printerr ("\nFAIL: %s\n", e.message);
|
||||
assert_not_reached ();
|
||||
} finally {
|
||||
if (stub != null)
|
||||
stub.stop ();
|
||||
}
|
||||
|
||||
h.done ();
|
||||
}
|
||||
|
||||
private class FakeStub : Object {
|
||||
public IOStream client_stream {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private SocketService service;
|
||||
private Cancellable cancellable = new Cancellable ();
|
||||
|
||||
private const string TARGET_XML =
|
||||
"<?xml version=\"1.0\"?>" +
|
||||
"<target version=\"1.0\">" +
|
||||
"<architecture>aarch64</architecture>" +
|
||||
"<feature name=\"org.gnu.gdb.aarch64.core\">" +
|
||||
"<reg name=\"x0\" bitsize=\"64\" regnum=\"0\"/>" +
|
||||
"<reg name=\"pc\" bitsize=\"64\" regnum=\"32\"/>" +
|
||||
"</feature>" +
|
||||
"</target>";
|
||||
|
||||
public static async FakeStub open () throws Error, IOError {
|
||||
var stub = new FakeStub ();
|
||||
yield stub.init ();
|
||||
return stub;
|
||||
}
|
||||
|
||||
private async void init () throws Error, IOError {
|
||||
service = new SocketService ();
|
||||
uint16 port;
|
||||
try {
|
||||
port = service.add_any_inet_port (null);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
service.incoming.connect ((connection) => {
|
||||
serve.begin (connection);
|
||||
return true;
|
||||
});
|
||||
service.start ();
|
||||
|
||||
var socket_client = new SocketClient ();
|
||||
try {
|
||||
client_stream = yield socket_client.connect_to_host_async ("127.0.0.1", port, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop () {
|
||||
cancellable.cancel ();
|
||||
service.stop ();
|
||||
}
|
||||
|
||||
private async void serve (IOStream connection) {
|
||||
var input = connection.get_input_stream ();
|
||||
var output = connection.get_output_stream ();
|
||||
try {
|
||||
string? request;
|
||||
while ((request = yield read_packet (input)) != null) {
|
||||
yield write_ack (output);
|
||||
yield write_packet (output, compute_reply (request));
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private string compute_reply (string request) {
|
||||
if (request.has_prefix ("qSupported"))
|
||||
return "PacketSize=1000;qXfer:features:read+";
|
||||
if (request.has_prefix ("qXfer:features:read:target.xml:"))
|
||||
return "l" + TARGET_XML;
|
||||
if (request == "qAttached")
|
||||
return "1";
|
||||
if (request == "?")
|
||||
return "T05thread:1;";
|
||||
if (request == "qC")
|
||||
return "QC1";
|
||||
if (request == "QThreadSuffixSupported")
|
||||
return "OK";
|
||||
if (request[0] == 'p')
|
||||
return is_thread_suffixed (request) ? "0000000000000000" : "E03";
|
||||
return "";
|
||||
}
|
||||
|
||||
private bool is_thread_suffixed (string request) {
|
||||
return ";thread:" in request;
|
||||
}
|
||||
|
||||
private async string? read_packet (InputStream input) throws GLib.Error {
|
||||
while (true) {
|
||||
int c = yield read_byte (input);
|
||||
if (c < 0)
|
||||
return null;
|
||||
if (c != '$')
|
||||
continue;
|
||||
|
||||
var payload = new StringBuilder ();
|
||||
while (true) {
|
||||
c = yield read_byte (input);
|
||||
if (c < 0)
|
||||
return null;
|
||||
if (c == '#')
|
||||
break;
|
||||
payload.append_c ((char) c);
|
||||
}
|
||||
yield read_byte (input);
|
||||
yield read_byte (input);
|
||||
return payload.str;
|
||||
}
|
||||
}
|
||||
|
||||
private async int read_byte (InputStream input) throws GLib.Error {
|
||||
uint8 buf[1];
|
||||
size_t n;
|
||||
yield input.read_all_async (buf, Priority.DEFAULT, cancellable, out n);
|
||||
if (n == 0)
|
||||
return -1;
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
private async void write_ack (OutputStream output) throws GLib.Error {
|
||||
yield write_all (output, "+".data);
|
||||
}
|
||||
|
||||
private async void write_packet (OutputStream output, string payload) throws GLib.Error {
|
||||
uint8 checksum = 0;
|
||||
for (int i = 0; i != payload.length; i++)
|
||||
checksum += (uint8) payload[i];
|
||||
var frame = "$%s#%02x".printf (payload, checksum);
|
||||
yield write_all (output, frame.data);
|
||||
}
|
||||
|
||||
private async void write_all (OutputStream output, uint8[] data) throws GLib.Error {
|
||||
size_t written;
|
||||
yield output.write_all_async (data, Priority.DEFAULT, cancellable, out written);
|
||||
}
|
||||
}
|
||||
|
||||
private class ThreadSuffixClient : GDB.Client {
|
||||
private ThreadSuffixClient (IOStream stream) {
|
||||
Object (stream: stream);
|
||||
}
|
||||
|
||||
public static new async ThreadSuffixClient open (IOStream stream, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var client = new ThreadSuffixClient (stream);
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
protected override async void detect_vendor_features (Cancellable? cancellable) throws Error, IOError {
|
||||
}
|
||||
|
||||
protected override async void enable_extensions (Cancellable? cancellable) throws Error, IOError {
|
||||
yield execute_simple ("QThreadSuffixSupported", cancellable);
|
||||
supported_features.add ("thread-suffix");
|
||||
}
|
||||
}
|
||||
|
||||
private class Harness : Frida.Test.AsyncHarness {
|
||||
public Harness (owned Frida.Test.AsyncHarness.TestSequenceFunc func) {
|
||||
base ((owned) func);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,13 @@ namespace Frida.HostSessionTest {
|
||||
#endif
|
||||
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
#if !QNX
|
||||
GLib.Test.add_func ("/HostSession/process-argv-should-be-available", () => {
|
||||
var h = new Harness ((h) => Local.process_argv_should_be_available.begin (h as Harness));
|
||||
h.run ();
|
||||
});
|
||||
#endif
|
||||
|
||||
#if LINUX
|
||||
GLib.Test.add_func ("/HostSession/Linux/backend", () => {
|
||||
var h = new Harness ((h) => Linux.backend.begin (h as Harness));
|
||||
@@ -1042,6 +1049,50 @@ namespace Frida.HostSessionTest {
|
||||
|
||||
namespace Local {
|
||||
|
||||
#if !QNX
|
||||
private static async void process_argv_should_be_available (Harness h) {
|
||||
try {
|
||||
var device_manager = new DeviceManager ();
|
||||
var device = yield device_manager.get_device_by_type (DeviceType.LOCAL);
|
||||
|
||||
var target_path = Frida.Test.Labrats.path_to_executable ("sleeper");
|
||||
var marker = "--frida-argv-probe";
|
||||
var target = Frida.Test.Process.start (target_path, new string[] { marker });
|
||||
|
||||
var options = new ProcessQueryOptions ();
|
||||
options.scope = METADATA;
|
||||
options.select_pid (target.id);
|
||||
|
||||
Process? match = null;
|
||||
var timer = new Timer ();
|
||||
while (match == null && timer.elapsed () < 5.0) {
|
||||
var processes = yield device.enumerate_processes (options);
|
||||
if (processes.size () > 0) {
|
||||
match = processes.get (0);
|
||||
} else {
|
||||
Timeout.add (50, process_argv_should_be_available.callback);
|
||||
yield;
|
||||
}
|
||||
}
|
||||
assert_true (match != null);
|
||||
|
||||
var argv = match.parameters["argv"];
|
||||
assert_true (argv != null);
|
||||
assert_true (argv.n_children () == 2);
|
||||
assert_true (argv.get_child_value (1).get_string () == marker);
|
||||
|
||||
target.kill ();
|
||||
|
||||
yield device_manager.close ();
|
||||
} catch (GLib.Error e) {
|
||||
printerr ("Oops: %s\n", e.message);
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
h.done ();
|
||||
}
|
||||
#endif
|
||||
|
||||
private static async void latency_should_be_nominal (Harness h) {
|
||||
h.disable_timeout ();
|
||||
|
||||
|
||||
@@ -222,4 +222,16 @@ namespace Libbpf {
|
||||
public int num_possible_cpus ();
|
||||
|
||||
public int strerror (int err, char[] buf);
|
||||
|
||||
public PrintFn? set_print (PrintFn? fn);
|
||||
|
||||
[CCode (cname = "libbpf_print_fn_t", has_target = false)]
|
||||
public delegate int PrintFn (PrintLevel level, string format, va_list args);
|
||||
|
||||
[CCode (cname = "enum libbpf_print_level", cprefix = "LIBBPF_", has_type_id = false)]
|
||||
public enum PrintLevel {
|
||||
WARN,
|
||||
INFO,
|
||||
DEBUG
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user