Files
frida-core/server/server.vala
Ole André Vadla Ravnås f67d1e81df Dump alive tasks every 30s in server and agent
Route the dump through GLib.info() using the new
g_task_describe_alive_tasks(), so the agent's dump reaches
logcat where its stdio is not visible. Bump the GLib wrap to
the branch holding that function.
2026-07-29 18:12:10 +02:00

442 lines
11 KiB
Vala

namespace Frida.Server {
private static Application application;
private const string DEFAULT_DIRECTORY = "re.frida.server";
private static bool output_version = false;
private static string? device_id = null;
private static string? listen_address = null;
private static string? certpath = null;
private static string? origin = null;
private static string? token = null;
private static string? asset_root = null;
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;
private static bool report_crashes = true;
private static bool verbose = false;
private enum PolicySoftenerFlavor {
SYSTEM,
INTERNAL;
public static PolicySoftenerFlavor from_nick (string nick) throws Error {
return Marshal.enum_from_nick<PolicySoftenerFlavor> (nick);
}
}
private delegate void ReadyHandler (bool success);
const OptionEntry[] option_entries = {
{ "version", 0, 0, OptionArg.NONE, ref output_version, "Output version information and exit", null },
{ "device", 0, 0, OptionArg.STRING, ref device_id, "Serve device with the given ID", "ID" },
{ "listen", 'l', 0, OptionArg.STRING, ref listen_address, "Listen on ADDRESS", "ADDRESS" },
{ "certificate", 0, 0, OptionArg.FILENAME, ref certpath, "Enable TLS using CERTIFICATE", "CERTIFICATE" },
{ "origin", 0, 0, OptionArg.STRING, ref origin, "Only accept requests with “Origin” header matching ORIGIN " +
"(by default any origin will be accepted)", "ORIGIN" },
{ "token", 0, 0, OptionArg.STRING, ref token, "Require authentication using TOKEN", "TOKEN" },
{ "asset-root", 0, 0, OptionArg.FILENAME, ref asset_root, "Serve static files inside ROOT (by default no files are served)",
"ROOT" },
{ "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 },
{ "ignore-crashes", 'C', OptionFlags.REVERSE, OptionArg.NONE, ref report_crashes,
"Disable native crash reporter integration", null },
{ "verbose", 'v', 0, OptionArg.NONE, ref verbose, "Be verbose", null },
{ null }
};
private static int main (string[] args) {
Environment.init ();
#if DARWIN
if (Path.get_basename (args[0]) == "frida-policyd") {
return Policyd._main ();
}
#endif
try {
var ctx = new OptionContext ();
ctx.set_help_enabled (true);
ctx.add_main_entries (option_entries, null);
ctx.parse (ref args);
} catch (OptionError e) {
printerr ("%s\n", e.message);
printerr ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
return 1;
}
if (output_version) {
stdout.printf ("%s\n", version_string ());
return 0;
}
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,
(token != null) ? new StaticAuthenticationService (token) : null,
(asset_root != null) ? File.new_for_path (asset_root) : null);
} catch (GLib.Error e) {
printerr ("%s\n", e.message);
return 2;
}
var options = new ControlServiceOptions ();
options.enable_preload = enable_preload;
options.report_crashes = report_crashes;
#if (IOS || TVOS) && !HAVE_EMBEDDED_ASSETS
string? program_path = null;
Gum.Process.enumerate_modules (m => {
uint32 * file_type = (uint32 *) (m.range.base_address + 12);
const uint32 MH_EXECUTE = 2;
if (*file_type == MH_EXECUTE) {
program_path = m.path;
return false;
}
return true;
});
int prefix_pos = program_path.last_index_of (Config.FRIDA_PREFIX + "/");
if (prefix_pos != -1 && prefix_pos != 0)
TemporaryDirectory.use_sysroot (program_path[:prefix_pos]);
#endif
PolicySoftenerFlavor softener_flavor = SYSTEM;
if (softener_flavor_str != null) {
try {
softener_flavor = PolicySoftenerFlavor.from_nick (softener_flavor_str);
} catch (Error e) {
printerr ("%s\n", e.message);
return 3;
}
}
#if IOS || TVOS
if (softener_flavor == INTERNAL)
InternalIOSTVOSPolicySoftener.enable ();
#endif
ReadyHandler? on_ready = null;
#if !WINDOWS && !TVOS
if (daemonize) {
var sync_fds = new int[2];
try {
Unix.open_pipe (sync_fds, 0);
Unix.set_fd_nonblocking (sync_fds[0], true);
Unix.set_fd_nonblocking (sync_fds[1], true);
} catch (GLib.Error e) {
assert_not_reached ();
}
var sync_in = new UnixInputStream (sync_fds[0], true);
var sync_out = new UnixOutputStream (sync_fds[1], true);
var pid = Posix.fork ();
if (pid != 0) {
try {
var status = new uint8[1];
sync_in.read (status);
return status[0];
} catch (GLib.Error e) {
return 4;
}
}
sync_in = null;
on_ready = (success) => {
if (success) {
Posix.setsid ();
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
Posix.dup2 (null_in, Posix.STDIN_FILENO);
Posix.close (null_in);
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];
status[0] = success ? 0 : 1;
try {
sync_out.write (status);
} catch (GLib.Error e) {
}
sync_out = null;
};
}
#endif
Environment.configure ();
#if DARWIN
var worker = new Thread<int> ("frida-server-main-loop", () => {
var exit_code = run_application (device_id, endpoint_params, options, on_ready);
_stop_run_loop ();
return exit_code;
});
_start_run_loop ();
var exit_code = worker.join ();
return exit_code;
#else
return run_application (device_id, endpoint_params, options, on_ready);
#endif
}
private static int run_application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options,
ReadyHandler on_ready) {
TemporaryDirectory.always_use ((directory != null) ? directory : DEFAULT_DIRECTORY);
application = new Application (device_id, endpoint_params, options);
Posix.signal (Posix.Signal.INT, (sig) => {
application.stop ();
});
Posix.signal (Posix.Signal.TERM, (sig) => {
application.stop ();
});
if (on_ready != null) {
application.ready.connect (success => {
on_ready (success);
on_ready = null;
});
}
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);
public extern void configure ();
}
#if DARWIN
public extern void _start_run_loop ();
public extern void _stop_run_loop ();
namespace Policyd {
public extern int _main ();
}
#endif
private sealed class Application : Object {
public signal void ready (bool success);
public string? device_id {
get;
construct;
}
public EndpointParameters endpoint_params {
get;
construct;
}
public ControlServiceOptions options {
get;
construct;
}
private DeviceManager? manager;
private ControlService? service;
private Cancellable io_cancellable = new Cancellable ();
private MainLoop loop = new MainLoop ();
private int exit_code;
private bool stopping;
public Application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options) {
Object (
device_id: device_id,
endpoint_params: endpoint_params,
options: options
);
}
public int run () {
Idle.add (() => {
start.begin ();
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 ();
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") {
manager = new DeviceManager.with_nonlocal_backends_only ();
var device = yield manager.get_device_by_id (device_id, 0, io_cancellable);
device.lost.connect (on_device_lost);
service = yield new ControlService.with_device (device, endpoint_params, options);
} else {
service = new ControlService (endpoint_params, options);
}
yield service.start (io_cancellable);
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
return;
printerr ("Unable to start: %s\n", e.message);
exit_code = 5;
loop.quit ();
ready (false);
return;
}
Idle.add (() => {
ready (true);
return false;
});
}
public void stop () {
Idle.add (() => {
perform_stop.begin ();
return false;
});
}
private async void perform_stop () {
if (stopping)
return;
stopping = true;
io_cancellable.cancel ();
try {
if (service != null) {
yield service.stop ();
service = null;
}
if (manager != null) {
yield manager.close ();
manager = null;
}
} catch (GLib.Error e) {
}
Idle.add (() => {
loop.quit ();
return false;
});
}
private void on_device_lost () {
stop ();
}
}
private TlsCertificate? parse_certificate (string? path) throws GLib.Error {
if (path == null)
return null;
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 ();
}