mirror of
https://github.com/frida/frida-core.git
synced 2026-11-09 01:59:34 -10:00
Compare commits
No files matched your search
+221
-14
@@ -16,13 +16,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { id: windows-x86_64, runner: '"windows-latest"' }
|
||||
- { id: windows-x86, runner: '"windows-latest"' }
|
||||
- { id: macos-x86_64, runner: '"macos-latest"' }
|
||||
- { id: macos-arm64, runner: '"macos-latest"' }
|
||||
- { id: linux-x86_64, runner: '"ubuntu-latest"' }
|
||||
- { id: linux-x86, runner: '"ubuntu-latest"' }
|
||||
- { id: freebsd-arm64, runner: '["self-hosted", "freebsd", "arm64"]' }
|
||||
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
|
||||
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
|
||||
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
|
||||
- { id: macos-x86_64, runner: '"macos-latest"' }
|
||||
- { id: macos-arm64, runner: '"macos-latest"' }
|
||||
- { id: linux-x86_64, runner: '"ubuntu-latest"' }
|
||||
- { id: linux-x86, runner: '"ubuntu-latest"' }
|
||||
fail-fast: false
|
||||
runs-on: ${{ fromJSON(matrix.runner) }}
|
||||
steps:
|
||||
@@ -30,6 +30,18 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install MinGW toolchain for cgo support
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
id: msys2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
install: ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Set up Apple certificates
|
||||
if: ${{ startsWith(matrix.id, 'macos-') }}
|
||||
uses: ./.github/actions/setup-apple-certificates
|
||||
@@ -48,18 +60,29 @@ jobs:
|
||||
- name: Build
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
run: |
|
||||
.\configure ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
.\configure `
|
||||
--host=${{ matrix.id }} `
|
||||
--enable-compiler-backend `
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
.\make
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
- name: Build
|
||||
if: ${{ !startsWith(matrix.id, 'windows-') && matrix.id != 'linux-x86' }}
|
||||
run: |
|
||||
./configure ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Build
|
||||
if: matrix.id == 'linux-x86'
|
||||
run: |
|
||||
CC="gcc -m32" CXX="g++ -m32" STRIP="strip" \
|
||||
./configure --build=linux-x86 --host=linux-x86 ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--build=linux-x86 \
|
||||
--host=linux-x86 \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
@@ -68,6 +91,7 @@ jobs:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
- name: Test
|
||||
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
|
||||
run: make test
|
||||
|
||||
cross:
|
||||
@@ -91,9 +115,17 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Build
|
||||
run: |
|
||||
./configure ${{ matrix.opts }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
${{ matrix.opts }} \
|
||||
${{ !contains(matrix.id, 'mips') && '--enable-compiler-backend' || '' }} \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
@@ -114,9 +146,17 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=$XTOOLS_HOST ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--host=$XTOOLS_HOST \
|
||||
${{ !startsWith(matrix.arch, 'mips') && '--enable-compiler-backend' || '' }} \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -127,6 +167,131 @@ jobs:
|
||||
if: matrix.arch == 'x86' || matrix.arch == 'x86_64'
|
||||
run: make test
|
||||
|
||||
emulator:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [armbe8, arm64be]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=$XTOOLS_HOST --without-prebuilds=sdk:host --enable-tests
|
||||
make
|
||||
- name: Run custom container
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: docker:stable
|
||||
options: -v ${{ github.workspace }}:/workspace:delegated
|
||||
run: |
|
||||
cat <<EOF > /workspace/initscript
|
||||
#!/bin/sh
|
||||
dmesg -n1
|
||||
mount -t proc none /proc
|
||||
mount -t sysfs none /sys
|
||||
mount -t tmpfs none /tmp
|
||||
mount -t devtmpfs none /dev
|
||||
mount -t 9p -o rw,sync,dirsync,relatime,trans=virtio,msize=131072,version=9p2000.L host0 /workspace
|
||||
ip link set lo up
|
||||
ip addr add 192.168.76.20/24 dev eth0
|
||||
ip link set eth0 up
|
||||
ip route add default via 192.168.76.2 dev eth0
|
||||
export LD_LIBRARY_PATH=/lib:/lib64:/libilp32
|
||||
/sbin/entropy
|
||||
cat /etc/motd
|
||||
export PS1='\u@frida:\w\$ '
|
||||
/root/frida-tests
|
||||
status=\$?
|
||||
echo "exit status: \$status"
|
||||
if [ \$status -eq 0 ]; then
|
||||
echo EXIT_OK
|
||||
elif [ \$status -gt 128 ]; then
|
||||
signal=\$((\$status - 128))
|
||||
echo "EXIT_FAIL (terminated by signal \$signal)"
|
||||
else
|
||||
echo "EXIT_FAIL (exit code \$status)"
|
||||
fi
|
||||
EOF
|
||||
|
||||
cat <<EOF > /workspace/launch.sh
|
||||
#!/bin/sh
|
||||
LOGFILE=/workspace/qemu.log
|
||||
: > "\$LOGFILE"
|
||||
timeout 120 qemu-system-aarch64 \
|
||||
-M virt \
|
||||
-machine virtualization=true \
|
||||
-machine type=virt \
|
||||
-cpu cortex-a72 \
|
||||
-smp 4 \
|
||||
-kernel /root/Image.gz \
|
||||
-initrd /root/initramfs.img \
|
||||
-append 'earlyprintk=serial,ttyAMA0 console=ttyAMA0 coredump_filter=0x3f' \
|
||||
-m 4096M \
|
||||
-net nic,id=eth \
|
||||
-net user,id=mynet,net=192.168.76.0/24 \
|
||||
-virtfs local,path=/workspace/,mount_tag=host0,security_model=passthrough,id=host0 \
|
||||
-nographic \
|
||||
-no-reboot \
|
||||
>> "\$LOGFILE" 2>&1 &
|
||||
QEMU_PID=\$!
|
||||
echo "QEMU started with PID \$QEMU_PID"
|
||||
tail -n 0 -f "\$LOGFILE" | while read line; do
|
||||
echo "\$line"
|
||||
echo "\$line" | grep "EXIT_OK" >/dev/null 2>&1
|
||||
if [ \$? -eq 0 ]; then
|
||||
echo "Success signal received from guest. Killing QEMU..."
|
||||
kill -9 "\$QEMU_PID"
|
||||
cat -n "\$LOGFILE"
|
||||
exit 0
|
||||
fi
|
||||
echo "\$line" | grep "EXIT_FAIL" >/dev/null 2>&1
|
||||
if [ \$? -eq 0 ]; then
|
||||
echo "Failure signal received from guest. Killing QEMU..."
|
||||
kill -9 "\$QEMU_PID"
|
||||
cat -n "\$LOGFILE"
|
||||
exit 1
|
||||
fi
|
||||
if ! kill -0 "\$QEMU_PID" 2>/dev/null; then
|
||||
echo "QEMU process has exited."
|
||||
cat -n "\$LOGFILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
EOF
|
||||
|
||||
cat <<EOF > /workspace/Dockerfile.ci
|
||||
FROM ghcr.io/frida/x-tools-linux-be-target:latest
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
cpio
|
||||
|
||||
RUN mkdir /tmp/initramfs
|
||||
WORKDIR /tmp/initramfs
|
||||
RUN cpio --extract < /root/initramfs.img
|
||||
|
||||
RUN mkdir /tmp/initramfs/workspace
|
||||
|
||||
COPY initscript /tmp/initramfs/init
|
||||
RUN chmod +x /tmp/initramfs/init
|
||||
|
||||
COPY build/tests /tmp/initramfs/root/
|
||||
RUN find . | cpio -o --format=newc -R root:root > /root/initramfs.img
|
||||
|
||||
COPY launch.sh /root/launch.sh
|
||||
RUN chmod +x /root/launch.sh
|
||||
ENTRYPOINT ["/root/launch.sh"]
|
||||
EOF
|
||||
|
||||
docker build -t ci-image -f /workspace/Dockerfile.ci /workspace
|
||||
docker run --rm -v /workspace:/workspace ci-image
|
||||
|
||||
mobile:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -143,6 +308,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Set up Apple certificates
|
||||
if: startsWith(matrix.id, 'ios-')
|
||||
uses: ./.github/actions/setup-apple-certificates
|
||||
@@ -160,7 +330,10 @@ jobs:
|
||||
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=${{ matrix.id }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--host=${{ matrix.id }} \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -250,6 +423,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
@@ -263,7 +437,40 @@ jobs:
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output="$(git status --porcelain)"
|
||||
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
echo "Diff:"
|
||||
git diff
|
||||
exit 1
|
||||
else
|
||||
echo "No unexpected changes detected."
|
||||
fi
|
||||
|
||||
# This job is used to check that the Linux labrats are built correctly for all
|
||||
# supported architectures. We do this because the binary artifacts are checked
|
||||
# in, and we want to ensure that they are up-to-date with respect to the source
|
||||
# for all architectures.
|
||||
check-labrats:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
./tests/labrats/rebuild.sh ${{ matrix.arch }}
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output=$(git status --porcelain src/linux/helpers/{bootstrapper,loader}-${{ matrix.arch }}.bin)
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
/src/barebone/helpers/memory-scanner/target/
|
||||
/src/barebone/helpers/target/
|
||||
/src/barebone/node_modules/
|
||||
/src/barebone/script-runtime.js
|
||||
/src/compiler/node_modules/
|
||||
/src/darwin/agent/osanalytics/node_modules/
|
||||
/src/darwin/agent/osanalytics/osanalytics.js
|
||||
/src/darwin/agent/reportcrash/node_modules/
|
||||
/src/darwin/agent/reportcrash/reportcrash.js
|
||||
/src/droidy/helper/build/
|
||||
/src/fruity/helpers/build/
|
||||
/src/linux/agent/system-server/node_modules/
|
||||
/src/linux/agent/system-server/system-server.js
|
||||
/src/linux/helpers/build/
|
||||
/src/linux/helpers/ext/
|
||||
/subprojects/*
|
||||
@@ -435,6 +435,7 @@ def compile(privdir: Path, state: State):
|
||||
environ={**build_env, **group.extra_environ},
|
||||
allowed_prebuilds=state.allowed_prebuilds,
|
||||
extra_meson_options=[
|
||||
"-Dcompiler_backend=disabled",
|
||||
"-Dhelper_modern=",
|
||||
"-Dhelper_legacy=",
|
||||
"-Dagent_modern=",
|
||||
@@ -499,6 +500,8 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
|
||||
if k.is_project():
|
||||
if is_for_us:
|
||||
if k.name == "compiler_backend":
|
||||
return False
|
||||
tokens = k.name.split("_")
|
||||
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
|
||||
return False
|
||||
|
||||
+2
-2
@@ -147,7 +147,7 @@ namespace Frida.Inject {
|
||||
public extern void init ();
|
||||
}
|
||||
|
||||
public class Application : Object {
|
||||
public sealed class Application : Object {
|
||||
public string? device_id {
|
||||
get;
|
||||
construct;
|
||||
@@ -426,7 +426,7 @@ namespace Frida.Inject {
|
||||
#endif
|
||||
}
|
||||
|
||||
private class ScriptRunner : Object, RpcPeer {
|
||||
private sealed class ScriptRunner : Object, RpcPeer {
|
||||
public TerminalMode terminal_mode {
|
||||
get;
|
||||
private set;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Frida.Agent {
|
||||
PROCESS_TRANSITION
|
||||
}
|
||||
|
||||
private class Runner : Object, ProcessInvader, AgentSessionProvider, ExitHandler, ForkHandler, SpawnHandler {
|
||||
private sealed class Runner : Object, ProcessInvader, AgentSessionProvider, ExitHandler, ForkHandler, SpawnHandler {
|
||||
public static Runner shared_instance = null;
|
||||
public static Mutex shared_mutex;
|
||||
private static string? cached_agent_path = null;
|
||||
@@ -1621,7 +1621,7 @@ namespace Frida.Agent {
|
||||
}
|
||||
#endif
|
||||
|
||||
private class LiveAgentSession : BaseAgentSession {
|
||||
private sealed class LiveAgentSession : BaseAgentSession {
|
||||
public uint registration_id {
|
||||
get;
|
||||
set;
|
||||
|
||||
+17
-4
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class BufferBuilder : Object {
|
||||
public sealed class BufferBuilder : Object {
|
||||
public ByteOrder byte_order {
|
||||
get;
|
||||
construct;
|
||||
@@ -307,7 +307,7 @@ namespace Frida {
|
||||
NUL
|
||||
}
|
||||
|
||||
public class Buffer : Object {
|
||||
public sealed class Buffer : Object {
|
||||
public Bytes bytes {
|
||||
get;
|
||||
construct;
|
||||
@@ -446,7 +446,14 @@ namespace Frida {
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) throws Error {
|
||||
string * start = (string *) get_pointer (offset, size);
|
||||
string val = start->substring (0, (long) size);
|
||||
size_t max_length = size - offset;
|
||||
string * end = memchr (start, 0, max_length);
|
||||
size_t n;
|
||||
if (end != null)
|
||||
n = end - start;
|
||||
else
|
||||
n = size;
|
||||
string val = start->substring (0, (long) n);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
@@ -466,7 +473,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferReader {
|
||||
public sealed class BufferReader {
|
||||
public size_t available {
|
||||
get {
|
||||
return buffer.bytes.get_size () - offset;
|
||||
@@ -579,6 +586,12 @@ namespace Frida {
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned BufferReader skip (size_t n) throws Error {
|
||||
check_available (n);
|
||||
offset += n;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
namespace Frida {
|
||||
public sealed class ChannelRegistry : Object {
|
||||
public signal void channel_closed (ChannelId id);
|
||||
|
||||
private Gee.Map<ChannelId?, weak IOStream> channels =
|
||||
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
|
||||
|
||||
~ChannelRegistry () {
|
||||
clear ();
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
foreach (IOStream stream in channels.values.to_array ()) {
|
||||
var channel_stream = stream as ChannelStream;
|
||||
if (channel_stream != null)
|
||||
channel_stream.abandon ();
|
||||
}
|
||||
channels.clear ();
|
||||
}
|
||||
|
||||
public void register (ChannelId id, IOStream stream) {
|
||||
stream.set_data ("channel-registry-entry", new Entry (this, id));
|
||||
lock (channels)
|
||||
channels[id] = stream;
|
||||
}
|
||||
|
||||
public IOStream link (ChannelId id) throws Error {
|
||||
IOStream? stream;
|
||||
lock (channels)
|
||||
stream = channels[id];
|
||||
if (stream == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void unlink (ChannelId id) {
|
||||
lock (channels) {
|
||||
if (!channels.unset (id))
|
||||
return;
|
||||
}
|
||||
|
||||
channel_closed (id);
|
||||
}
|
||||
|
||||
private class Entry {
|
||||
public ChannelRegistry parent;
|
||||
public ChannelId id;
|
||||
|
||||
public Entry (ChannelRegistry parent, ChannelId id) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
~Entry () {
|
||||
parent.unlink (id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChannelEndpoint : Object, Channel {
|
||||
private IOStream? stream;
|
||||
|
||||
private ByteArray write_queue = new ByteArray ();
|
||||
private bool writing = false;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public ChannelEndpoint (IOStream stream) {
|
||||
this.stream = stream;
|
||||
|
||||
read_loop.begin ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws Error, IOError {
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
public async void input (uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (io_cancellable.is_cancelled ())
|
||||
throw new Error.INVALID_OPERATION ("Channel is closed");
|
||||
|
||||
write_queue.append (data);
|
||||
|
||||
if (!writing) {
|
||||
writing = true;
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
process_write_queue.begin ();
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private async void read_loop () {
|
||||
var input = stream.get_input_stream ();
|
||||
var buffer = new uint8[64 * 1024];
|
||||
|
||||
while (true) {
|
||||
ssize_t n;
|
||||
try {
|
||||
n = yield input.read_async (buffer, Priority.DEFAULT, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
break;
|
||||
}
|
||||
|
||||
output (buffer[:n]);
|
||||
|
||||
if (n == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
stream.close_async.begin ();
|
||||
stream = null;
|
||||
}
|
||||
|
||||
private async void process_write_queue () {
|
||||
if (stream != null) {
|
||||
var output = stream.get_output_stream ();
|
||||
|
||||
while (write_queue.len > 0) {
|
||||
uint8[] batch = write_queue.steal ();
|
||||
|
||||
size_t bytes_written;
|
||||
try {
|
||||
yield output.write_all_async (batch, Priority.DEFAULT, io_cancellable, out bytes_written);
|
||||
} catch (GLib.Error e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ChannelStream : VirtualStream {
|
||||
public Channel channel {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private ByteArray recv_queue = new ByteArray ();
|
||||
private ByteArray send_queue = new ByteArray ();
|
||||
|
||||
public ChannelStream (Channel channel) {
|
||||
Object (channel: channel);
|
||||
}
|
||||
|
||||
construct {
|
||||
channel.output.connect (on_output);
|
||||
}
|
||||
|
||||
public void abandon () {
|
||||
with_state_lock (() => {
|
||||
state = CLOSED;
|
||||
update_pending_io ();
|
||||
});
|
||||
}
|
||||
|
||||
protected override IOCondition query_events () {
|
||||
IOCondition new_events = 0;
|
||||
|
||||
if (recv_queue.len != 0 || state == CLOSED)
|
||||
new_events |= IN;
|
||||
|
||||
if (state == OPEN)
|
||||
new_events |= OUT;
|
||||
|
||||
return new_events;
|
||||
}
|
||||
|
||||
protected override void handle_close () {
|
||||
channel.close.begin (null);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer) throws IOError {
|
||||
ssize_t n = 0;
|
||||
with_state_lock (() => {
|
||||
n = ssize_t.min (recv_queue.len, buffer.length);
|
||||
if (n > 0) {
|
||||
Memory.copy (buffer, recv_queue.data, n);
|
||||
recv_queue.remove_range (0, (uint) n);
|
||||
|
||||
update_pending_io ();
|
||||
} else {
|
||||
if (state == OPEN)
|
||||
n = -1;
|
||||
}
|
||||
});
|
||||
|
||||
if (n == -1)
|
||||
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer) throws IOError {
|
||||
with_state_lock (() => {
|
||||
send_queue.append (buffer);
|
||||
});
|
||||
|
||||
if (main_context.is_owner ()) {
|
||||
process_send_queue ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
process_send_queue ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
private void process_send_queue () {
|
||||
uint8[]? chunk = null;
|
||||
with_state_lock (() => {
|
||||
size_t n = send_queue.len;
|
||||
if (n == 0)
|
||||
return;
|
||||
chunk = send_queue.data[0:n];
|
||||
send_queue.remove_range (0, (uint) n);
|
||||
});
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
channel.input.begin (chunk, io_cancellable);
|
||||
}
|
||||
|
||||
private void on_output (uint8[] data) {
|
||||
with_state_lock (() => {
|
||||
if (data.length != 0)
|
||||
recv_queue.append (data);
|
||||
else
|
||||
state = CLOSED;
|
||||
update_pending_io ();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class CFArray<T> {
|
||||
public sealed class CFArray<T> {
|
||||
private CoreFoundation.Array handle;
|
||||
|
||||
public static CFArray<T> wrap<T> (void * handle) {
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ namespace Frida {
|
||||
return yield connection.get_proxy (null, ObjectPath.HOST_SESSION, DBusProxyFlags.NONE, cancellable);
|
||||
}
|
||||
|
||||
private class DummyInputStream : InputStream {
|
||||
private sealed class DummyInputStream : InputStream {
|
||||
private bool done = false;
|
||||
private Mutex mutex;
|
||||
private Cond cond;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
namespace Frida.FS {
|
||||
public void mkdirp (File dir, Cancellable? cancellable = null) throws Error {
|
||||
try {
|
||||
dir.make_directory_with_parents (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void rmtree (File dir, Cancellable? cancellable = null) throws Error {
|
||||
rmtree_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private void rmtree_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error {
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child, cancellable) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child, cancellable);
|
||||
else
|
||||
child.delete (cancellable);
|
||||
}
|
||||
|
||||
dir.delete (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void rmtree_async (File dir, Cancellable? cancellable = null) throws Error, IOError {
|
||||
yield rmtree_async_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private async void rmtree_async_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error, IOError {
|
||||
int io_priority = Priority.DEFAULT;
|
||||
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, io_priority,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var batch = yield enumerator.next_files_async (1000, io_priority, cancellable);
|
||||
if (batch.is_empty ())
|
||||
break;
|
||||
foreach (FileInfo info in batch) {
|
||||
File child = dir.get_child (info.get_name ());
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
yield rmtree_async_worker (child, depth + 1, cancellable);
|
||||
else
|
||||
yield child.delete_async (io_priority, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
yield dir.delete_async (io_priority, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async string read_all_text (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes b = yield read_all_bytes (f, cancellable);
|
||||
string text = (string) b.get_data ();
|
||||
if (!text.validate ())
|
||||
throw new Error.PROTOCOL ("Unable to read '%s': invalid UTF-8", f.get_parse_name ());
|
||||
return text;
|
||||
}
|
||||
|
||||
public async void write_all_text (File f, string t, Cancellable? cancellable) throws Error, IOError {
|
||||
yield write_all_bytes (f, new Bytes (t.data), cancellable);
|
||||
}
|
||||
|
||||
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
return yield f.load_bytes_async (cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void write_all_bytes (File f, Bytes b, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
yield f.replace_contents_bytes_async (b, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef __FRIDA_SYSCALL_H__
|
||||
#define __FRIDA_SYSCALL_H__
|
||||
#ifndef __FRIDA_LINUX_SYSCALL_H__
|
||||
#define __FRIDA_LINUX_SYSCALL_H__
|
||||
|
||||
#include <linux/unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -14,7 +14,7 @@ namespace Frida {
|
||||
[CCode (cheader_filename = "sys/mman.h", cname = "MAP_ANONYMOUS")]
|
||||
public const int MAP_ANONYMOUS;
|
||||
|
||||
[CCode (cheader_filename = "linux/frida-syscall.h", cprefix = "SYS_", has_type_id = false)]
|
||||
[CCode (cheader_filename = "linux-syscall.h", cprefix = "SYS_", has_type_id = false)]
|
||||
public enum SysCall {
|
||||
memfd_create,
|
||||
pidfd_getfd,
|
||||
@@ -0,0 +1,107 @@
|
||||
namespace Frida {
|
||||
private uint linux_major = 0;
|
||||
private uint linux_minor = 0;
|
||||
|
||||
public bool check_kernel_version (uint major, uint minor) {
|
||||
if (linux_major == 0) {
|
||||
var name = Posix.utsname ();
|
||||
name.release.scanf ("%u.%u", out linux_major, out linux_minor);
|
||||
}
|
||||
|
||||
return (linux_major == major && linux_minor >= minor) || linux_major > major;
|
||||
}
|
||||
|
||||
public class FileDescriptor : Object, FileDescriptorBased {
|
||||
public int handle;
|
||||
|
||||
public FileDescriptor (int handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
~FileDescriptor () {
|
||||
if (handle != -1)
|
||||
Posix.close (handle);
|
||||
}
|
||||
|
||||
public int steal () {
|
||||
int result = handle;
|
||||
handle = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int get_fd () {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PidFileDescriptor : FileDescriptor {
|
||||
private uint pid;
|
||||
|
||||
private PidFileDescriptor (int fd, uint pid) {
|
||||
base (fd);
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public static bool is_supported () {
|
||||
return check_kernel_version (5, 3);
|
||||
}
|
||||
|
||||
public static bool getfd_is_supported () {
|
||||
return check_kernel_version (5, 6);
|
||||
}
|
||||
|
||||
public static PidFileDescriptor from_pid (uint pid) throws Error {
|
||||
int fd = pidfd_open (pid, 0);
|
||||
if (fd == -1)
|
||||
throw_pidfd_error (pid, errno);
|
||||
return new PidFileDescriptor (fd, pid);
|
||||
}
|
||||
|
||||
public FileDescriptor getfd (int targetfd) throws Error {
|
||||
int fd = pidfd_getfd (handle, targetfd, 0);
|
||||
if (fd == -1)
|
||||
throw_pidfd_error (pid, errno);
|
||||
return new FileDescriptor (fd);
|
||||
}
|
||||
|
||||
private static int pidfd_open (uint pid, uint flags) {
|
||||
return Linux.syscall (SysCall.pidfd_open, pid, flags);
|
||||
}
|
||||
|
||||
private static int pidfd_getfd (int pidfd, int targetfd, uint flags) {
|
||||
return Linux.syscall (SysCall.pidfd_getfd, pidfd, targetfd, flags);
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private static void throw_pidfd_error (uint pid, int err) throws Error {
|
||||
switch (err) {
|
||||
case Posix.ESRCH:
|
||||
throw new Error.PROCESS_NOT_FOUND ("Process not found");
|
||||
case Posix.EPERM:
|
||||
throw new Error.PERMISSION_DENIED ("Unable to use pidfd for pid %u: %s", pid, strerror (err));
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unable to use pidfd for pid %u: %s", pid, strerror (err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MemoryFileDescriptor {
|
||||
public bool is_supported () {
|
||||
return check_kernel_version (3, 17);
|
||||
}
|
||||
|
||||
public static FileDescriptor from_bytes (string name, Bytes bytes) {
|
||||
assert (is_supported ());
|
||||
|
||||
var fd = new FileDescriptor (memfd_create (name, 0));
|
||||
unowned uint8[] data = bytes.get_data ();
|
||||
ssize_t n = Posix.write (fd.handle, data, data.length);
|
||||
assert (n == data.length);
|
||||
return fd;
|
||||
}
|
||||
|
||||
private int memfd_create (string name, uint flags) {
|
||||
return Linux.syscall (SysCall.memfd_create, name, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -1,11 +1,15 @@
|
||||
base_sources = [
|
||||
'session.vala',
|
||||
'session-glue.c',
|
||||
'channel.vala',
|
||||
'service.vala',
|
||||
'stream.vala',
|
||||
'rpc.vala',
|
||||
'p2p.vala',
|
||||
'p2p-glue.c',
|
||||
'dbus.vala',
|
||||
'promise.vala',
|
||||
'fs.vala',
|
||||
'socket.vala',
|
||||
'socket-glue.c',
|
||||
'buffer.vala',
|
||||
@@ -25,6 +29,15 @@ if host_os_family == 'darwin'
|
||||
base_sources += 'corefoundation.vala'
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux'
|
||||
base_sources += 'linux.vala'
|
||||
extra_vala_args += [
|
||||
'--pkg=linux',
|
||||
'--pkg=linux-system',
|
||||
'--pkg=posix',
|
||||
]
|
||||
endif
|
||||
|
||||
extra_deps = []
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=gio-unix-2.0'
|
||||
@@ -42,6 +55,12 @@ if host_os_family == 'darwin'
|
||||
'--pkg=darwin-xpc',
|
||||
]
|
||||
endif
|
||||
if host_os_family == 'linux'
|
||||
base_vala_args += [
|
||||
'--vapidir=' + meson.current_source_dir(),
|
||||
'--pkg=gio-unix-2.0',
|
||||
]
|
||||
endif
|
||||
|
||||
base_gir_name = f'FridaBase-@[email protected]'
|
||||
|
||||
@@ -59,7 +78,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
base_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: base,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep],
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
|
||||
)
|
||||
|
||||
base_build_dir = meson.current_build_dir()
|
||||
|
||||
+35
-233
@@ -169,7 +169,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class PeerSocket : Object, DatagramBased {
|
||||
public sealed class PeerSocket : Object, DatagramBased {
|
||||
public Nice.Agent agent {
|
||||
get;
|
||||
construct;
|
||||
@@ -452,6 +452,8 @@ namespace Frida {
|
||||
if ((_pending_io & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
|
||||
notify_property ("pending-io");
|
||||
}
|
||||
|
||||
private void prepare_for_io (int64 timeout, Cancellable? cancellable, out int64 deadline) throws IOError {
|
||||
@@ -563,7 +565,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class SctpConnection : IOStream {
|
||||
public sealed class SctpConnection : VirtualStream {
|
||||
public DatagramBased transport_socket {
|
||||
get;
|
||||
construct;
|
||||
@@ -584,55 +586,14 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public State state {
|
||||
get {
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public override InputStream input_stream {
|
||||
get {
|
||||
return _input_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public override OutputStream output_stream {
|
||||
get {
|
||||
return _output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOCondition pending_io {
|
||||
get {
|
||||
lock (state)
|
||||
return sctp_events;
|
||||
}
|
||||
}
|
||||
|
||||
private State _state = CREATED;
|
||||
private SctpInputStream _input_stream;
|
||||
private SctpOutputStream _output_stream;
|
||||
|
||||
private DatagramBasedSource transport_source;
|
||||
private uint8[] transport_buffer = new uint8[65536];
|
||||
|
||||
private void * sctp_socket;
|
||||
private IOCondition sctp_events = 0;
|
||||
private SctpTimerSource sctp_source;
|
||||
private uint16 stream_id;
|
||||
private ByteArray dcep_message = new ByteArray ();
|
||||
|
||||
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public enum State {
|
||||
CREATED,
|
||||
OPENING,
|
||||
OPENED,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
public SctpConnection (DatagramBased transport_socket, PeerSetup setup, uint16 port, size_t max_message_size) {
|
||||
Object (
|
||||
transport_socket: transport_socket,
|
||||
@@ -649,14 +610,9 @@ namespace Frida {
|
||||
protected extern static void _initialize_sctp_backend ();
|
||||
|
||||
construct {
|
||||
_input_stream = new SctpInputStream (this);
|
||||
_output_stream = new SctpOutputStream (this);
|
||||
|
||||
sctp_socket = _create_sctp_socket ();
|
||||
_connect_sctp_socket (sctp_socket, port);
|
||||
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
|
||||
transport_source = transport_socket.create_source (IOCondition.IN, io_cancellable);
|
||||
transport_source.set_callback (on_transport_socket_readable);
|
||||
transport_source.attach (main_context);
|
||||
@@ -669,17 +625,22 @@ namespace Frida {
|
||||
|
||||
protected extern void _connect_sctp_socket (void * sock, uint16 port);
|
||||
|
||||
public override bool close (GLib.Cancellable? cancellable) throws IOError {
|
||||
do_close ();
|
||||
return true;
|
||||
protected override VirtualStream.State query_initial_state () {
|
||||
return CREATED;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
|
||||
do_close ();
|
||||
return true;
|
||||
protected override IOCondition query_events () {
|
||||
return _query_sctp_socket_events (sctp_socket);
|
||||
}
|
||||
|
||||
private void do_close () {
|
||||
protected override void update_pending_io () {
|
||||
base.update_pending_io ();
|
||||
sctp_source.invalidate ();
|
||||
}
|
||||
|
||||
protected extern static IOCondition _query_sctp_socket_events (void * sock);
|
||||
|
||||
protected override void handle_close () {
|
||||
sctp_source.destroy ();
|
||||
transport_source.destroy ();
|
||||
|
||||
@@ -689,13 +650,17 @@ namespace Frida {
|
||||
|
||||
public extern static void _close (void * sock);
|
||||
|
||||
public void shutdown (SctpShutdownType type) throws IOError {
|
||||
_shutdown (sctp_socket, type);
|
||||
public override void shutdown_read () throws IOError {
|
||||
_shutdown (sctp_socket, READ);
|
||||
}
|
||||
|
||||
public override void shutdown_write () throws IOError {
|
||||
_shutdown (sctp_socket, WRITE);
|
||||
}
|
||||
|
||||
public extern static void _shutdown (void * sock, SctpShutdownType type) throws IOError;
|
||||
|
||||
public ssize_t recv (uint8[] buffer) throws IOError {
|
||||
public override ssize_t read (uint8[] buffer) throws IOError {
|
||||
ssize_t n = -1;
|
||||
|
||||
try {
|
||||
@@ -711,18 +676,18 @@ namespace Frida {
|
||||
handle_dcep_message (stream_id, dcep_message.steal ());
|
||||
}
|
||||
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
|
||||
} else if (protocol_id == NONE || _state != OPENED) {
|
||||
} else if (protocol_id == NONE || state != OPEN) {
|
||||
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
|
||||
}
|
||||
} finally {
|
||||
update_sctp_events ();
|
||||
update_pending_io ();
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
public ssize_t send (uint8[] buffer) throws IOError {
|
||||
if (_state != OPENED)
|
||||
public override ssize_t write (uint8[] buffer) throws IOError {
|
||||
if (state != OPEN)
|
||||
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
|
||||
|
||||
ssize_t n = ssize_t.min (buffer.length, (ssize_t) max_message_size);
|
||||
@@ -730,20 +695,10 @@ namespace Frida {
|
||||
try {
|
||||
return _send (sctp_socket, stream_id, WEBRTC_BINARY, buffer[0:n]);
|
||||
} finally {
|
||||
update_sctp_events ();
|
||||
update_pending_io ();
|
||||
}
|
||||
}
|
||||
|
||||
public void register_source (Source source, IOCondition condition) {
|
||||
lock (state)
|
||||
sources[source] = condition | IOCondition.ERR | IOCondition.HUP;
|
||||
}
|
||||
|
||||
public void unregister_source (Source source) {
|
||||
lock (state)
|
||||
sources.unset (source);
|
||||
}
|
||||
|
||||
private bool on_transport_socket_readable (DatagramBased datagram_based, IOCondition condition) {
|
||||
var v = InputVector ();
|
||||
v.buffer = transport_buffer;
|
||||
@@ -783,9 +738,9 @@ namespace Frida {
|
||||
}
|
||||
|
||||
protected void _on_sctp_socket_events_changed () {
|
||||
update_sctp_events ();
|
||||
update_pending_io ();
|
||||
|
||||
if (_state == CREATED && setup == ACTIVE && (sctp_events & IOCondition.OUT) != 0) {
|
||||
if (state == CREATED && setup == ACTIVE && (pending_io & IOCondition.OUT) != 0) {
|
||||
stream_id = 1;
|
||||
|
||||
uint8[] open_message = {
|
||||
@@ -799,31 +754,12 @@ namespace Frida {
|
||||
};
|
||||
try {
|
||||
_send (sctp_socket, stream_id, WEBRTC_DCEP, open_message);
|
||||
_state = OPENING;
|
||||
state = OPENING;
|
||||
} catch (IOError e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update_sctp_events () {
|
||||
IOCondition new_events = _query_sctp_socket_events (sctp_socket);
|
||||
|
||||
lock (state) {
|
||||
sctp_events = new_events;
|
||||
|
||||
foreach (var entry in sources.entries) {
|
||||
unowned Source source = entry.key;
|
||||
IOCondition c = entry.value;
|
||||
if ((new_events & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
|
||||
sctp_source.invalidate ();
|
||||
}
|
||||
}
|
||||
|
||||
protected extern static IOCondition _query_sctp_socket_events (void * sock);
|
||||
|
||||
protected extern static ssize_t _recv (void * sock, uint8[] buffer, out uint16 stream_id,
|
||||
out PayloadProtocolId protocol_id, out SctpMessageFlags message_flags) throws IOError;
|
||||
|
||||
@@ -835,7 +771,7 @@ namespace Frida {
|
||||
|
||||
switch (type) {
|
||||
case DATA_CHANNEL_OPEN: {
|
||||
if (_state != CREATED || setup == ACTIVE)
|
||||
if (state != CREATED || setup == ACTIVE)
|
||||
return;
|
||||
|
||||
this.stream_id = stream_id;
|
||||
@@ -843,15 +779,15 @@ namespace Frida {
|
||||
uint8[] reply = { DcepMessageType.DATA_CHANNEL_ACK };
|
||||
_send (sctp_socket, stream_id, WEBRTC_DCEP, reply);
|
||||
|
||||
_state = OPENED;
|
||||
state = OPEN;
|
||||
|
||||
break;
|
||||
}
|
||||
case DATA_CHANNEL_ACK:
|
||||
if (_state != OPENING)
|
||||
if (state != OPENING)
|
||||
return;
|
||||
|
||||
_state = OPENED;
|
||||
state = OPEN;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -886,140 +822,6 @@ namespace Frida {
|
||||
DATA_CHANNEL_ACK = 0x02
|
||||
}
|
||||
|
||||
private class SctpInputStream : InputStream, PollableInputStream {
|
||||
public weak SctpConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public SctpInputStream (SctpConnection connection) {
|
||||
Object (connection: connection);
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
connection.shutdown (READ);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_readable () {
|
||||
return (connection.pending_io & IOCondition.IN) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new SctpIOSource (connection, IOCondition.IN), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
|
||||
return connection.recv (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private class SctpOutputStream : OutputStream, PollableOutputStream {
|
||||
public weak SctpConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public SctpOutputStream (SctpConnection connection) {
|
||||
Object (connection: connection);
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
connection.shutdown (WRITE);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_writable () {
|
||||
return (connection.pending_io & IOCondition.OUT) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new SctpIOSource (connection, IOCondition.OUT), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
|
||||
return connection.send (buffer);
|
||||
}
|
||||
|
||||
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class SctpIOSource : Source {
|
||||
public SctpConnection connection;
|
||||
public IOCondition condition;
|
||||
|
||||
public SctpIOSource (SctpConnection connection, IOCondition condition) {
|
||||
this.connection = connection;
|
||||
this.condition = condition;
|
||||
|
||||
connection.register_source (this, condition);
|
||||
}
|
||||
|
||||
~SctpIOSource () {
|
||||
connection.unregister_source (this);
|
||||
}
|
||||
|
||||
protected override bool prepare (out int timeout) {
|
||||
timeout = -1;
|
||||
return (connection.pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool check () {
|
||||
return (connection.pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool dispatch (SourceFunc? callback) {
|
||||
set_ready_time (-1);
|
||||
|
||||
if (callback == null)
|
||||
return Source.REMOVE;
|
||||
|
||||
return callback ();
|
||||
}
|
||||
|
||||
protected static bool closure_callback (Closure closure) {
|
||||
var return_value = Value (typeof (bool));
|
||||
|
||||
closure.invoke (ref return_value, {});
|
||||
|
||||
return return_value.get_boolean ();
|
||||
}
|
||||
}
|
||||
|
||||
private class SctpTimerSource : Source {
|
||||
private static int64 last_process_time = -1;
|
||||
|
||||
|
||||
+53
-13
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class Promise<T> {
|
||||
public sealed class Promise<T> {
|
||||
private Impl<T> impl;
|
||||
|
||||
public Future<T> future {
|
||||
@@ -46,7 +46,8 @@ namespace Frida {
|
||||
}
|
||||
private GLib.Error? _error;
|
||||
|
||||
private Gee.ArrayQueue<CompletionFuncEntry> on_complete;
|
||||
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
|
||||
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
|
||||
|
||||
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
|
||||
if (_ready)
|
||||
@@ -74,7 +75,21 @@ namespace Frida {
|
||||
return get_result ();
|
||||
}
|
||||
|
||||
private T get_result () throws Frida.Error, IOError {
|
||||
public void then (owned FutureCompletionHandler<T> handler) {
|
||||
if (_ready) {
|
||||
schedule (() => {
|
||||
handler (this);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_then == null)
|
||||
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
|
||||
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
|
||||
}
|
||||
|
||||
public T get_result () throws Frida.Error, IOError {
|
||||
if (error != null) {
|
||||
if (error is Frida.Error)
|
||||
throw (Frida.Error) error;
|
||||
@@ -111,19 +126,32 @@ namespace Frida {
|
||||
internal void transition_to_ready () {
|
||||
_ready = true;
|
||||
|
||||
if (on_complete != null && !on_complete.is_empty) {
|
||||
var source = new IdleSource ();
|
||||
source.set_priority (Priority.HIGH);
|
||||
source.set_callback (() => {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
return false;
|
||||
if (on_complete != null || on_then != null) {
|
||||
schedule (() => {
|
||||
if (on_complete != null) {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
}
|
||||
|
||||
if (on_then != null) {
|
||||
ThenHandlerEntry? entry;
|
||||
while ((entry = on_then.poll ()) != null)
|
||||
entry.handler (this);
|
||||
on_then = null;
|
||||
}
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private static void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionFuncEntry {
|
||||
@@ -133,6 +161,14 @@ namespace Frida {
|
||||
this.func = (owned) func;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThenHandlerEntry<T> {
|
||||
public FutureCompletionHandler<T> handler;
|
||||
|
||||
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
|
||||
this.handler = (owned) handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Future<T> : Object {
|
||||
@@ -140,5 +176,9 @@ namespace Frida {
|
||||
public abstract T? value { get; }
|
||||
public abstract GLib.Error? error { get; }
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
|
||||
public abstract void then (owned FutureCompletionHandler<T> handler);
|
||||
public abstract T get_result () throws Frida.Error, IOError;
|
||||
}
|
||||
|
||||
public delegate void FutureCompletionHandler<T> (Future<T> future);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class RpcClient : Object {
|
||||
public sealed class RpcClient : Object {
|
||||
public weak RpcPeer peer {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace Frida {
|
||||
public sealed class ServiceSessionRegistry : Object {
|
||||
public signal void session_closed (ServiceSessionId id);
|
||||
|
||||
private Gee.Map<ServiceSessionId?, ServiceSession> sessions =
|
||||
new Gee.HashMap<ServiceSessionId?, ServiceSession> (ServiceSessionId.hash, ServiceSessionId.equal);
|
||||
|
||||
~ServiceSessionRegistry () {
|
||||
clear ();
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
foreach (var session in sessions.values.to_array ())
|
||||
session.close ();
|
||||
sessions.clear ();
|
||||
}
|
||||
|
||||
public void register (ServiceSessionId id, ServiceSession session) {
|
||||
sessions[id] = session;
|
||||
|
||||
ServiceSessionId? boxed_id = id;
|
||||
session.set_data ("service-session-id", boxed_id);
|
||||
|
||||
session.close.connect (on_session_close);
|
||||
}
|
||||
|
||||
public ServiceSession link (ServiceSessionId id) throws Error {
|
||||
var session = sessions[id];
|
||||
if (session == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid session ID");
|
||||
return session;
|
||||
}
|
||||
|
||||
public void unlink (ServiceSessionId id) {
|
||||
ServiceSession? session;
|
||||
if (sessions.unset (id, out session)) {
|
||||
session.close.disconnect (on_session_close);
|
||||
|
||||
session_closed (id);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_session_close (ServiceSession session) {
|
||||
ServiceSessionId? id = session.get_data ("service-session-id");
|
||||
unlink (id);
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
-25
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
[DBus (name = "re.frida.HostSession16")]
|
||||
[DBus (name = "re.frida.HostSession17")]
|
||||
public interface HostSession : Object {
|
||||
public abstract async void ping (uint interval_seconds, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace Frida {
|
||||
public abstract async InjectorPayloadId inject_library_blob (uint pid, uint8[] blob, string entrypoint, string data,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async ChannelId open_channel (string address, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void child_added (HostChildInfo info);
|
||||
@@ -34,10 +38,12 @@ namespace Frida {
|
||||
public signal void process_crashed (CrashInfo crash);
|
||||
public signal void output (uint pid, int fd, uint8[] data);
|
||||
public signal void agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash);
|
||||
public signal void channel_closed (ChannelId id);
|
||||
public signal void service_session_closed (ServiceSessionId id);
|
||||
public signal void uninjected (InjectorPayloadId id);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AgentSessionProvider16")]
|
||||
[DBus (name = "re.frida.AgentSessionProvider17")]
|
||||
public interface AgentSessionProvider : Object {
|
||||
public abstract async void open (AgentSessionId id, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -52,7 +58,7 @@ namespace Frida {
|
||||
public signal void child_gating_changed (uint subscriber_count);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AgentSession16")]
|
||||
[DBus (name = "re.frida.AgentSession17")]
|
||||
public interface AgentSession : Object {
|
||||
public abstract async void close (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
@@ -94,7 +100,7 @@ namespace Frida {
|
||||
public signal void candidate_gathering_done ();
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AgentController16")]
|
||||
[DBus (name = "re.frida.AgentController17")]
|
||||
public interface AgentController : Object {
|
||||
#if !WINDOWS
|
||||
public abstract async HostChildId prepare_to_fork (uint parent_pid, Cancellable? cancellable, out uint parent_injectee_id,
|
||||
@@ -115,7 +121,7 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AgentMessageSink16")]
|
||||
[DBus (name = "re.frida.AgentMessageSink17")]
|
||||
public interface AgentMessageSink : Object {
|
||||
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -145,7 +151,7 @@ namespace Frida {
|
||||
DEBUGGER
|
||||
}
|
||||
|
||||
public class AgentMessageTransmitter : Object {
|
||||
public sealed class AgentMessageTransmitter : Object {
|
||||
public signal void closed ();
|
||||
public signal void new_candidates (string[] candidate_sdps);
|
||||
public signal void candidate_gathering_done ();
|
||||
@@ -743,13 +749,30 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.TransportBroker16")]
|
||||
[DBus (name = "re.frida.Channel17")]
|
||||
public interface Channel : Object {
|
||||
public abstract async void close (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void input (uint8[] data, Cancellable? cancellable) throws GLib.Error;
|
||||
public signal void output (uint8[] data);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.ServiceSession17")]
|
||||
public interface ServiceSession : Object {
|
||||
public signal void close ();
|
||||
public signal void message (Variant message);
|
||||
|
||||
public abstract async void activate (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void cancel (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async Variant request (Variant parameters, Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.TransportBroker17")]
|
||||
public interface TransportBroker : Object {
|
||||
public abstract async void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port,
|
||||
out string token) throws GLib.Error;
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.PortalSession16")]
|
||||
[DBus (name = "re.frida.PortalSession17")]
|
||||
public interface PortalSession : Object {
|
||||
public abstract async void join (HostApplicationInfo app, SpawnStartState current_state,
|
||||
AgentSessionId[] interrupted_sessions, HashTable<string, Variant> options, Cancellable? cancellable,
|
||||
@@ -758,19 +781,19 @@ namespace Frida {
|
||||
public signal void kill ();
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.BusSession16")]
|
||||
[DBus (name = "re.frida.BusSession17")]
|
||||
public interface BusSession : Object {
|
||||
public abstract async void attach (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void post (string json, bool has_data, uint8[] data, Cancellable? cancellable) throws GLib.Error;
|
||||
public signal void message (string json, bool has_data, uint8[] data);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AuthenticationService16")]
|
||||
[DBus (name = "re.frida.AuthenticationService17")]
|
||||
public interface AuthenticationService : Object {
|
||||
public abstract async string authenticate (string token, Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
public class StaticAuthenticationService : Object, AuthenticationService {
|
||||
public sealed class StaticAuthenticationService : Object, AuthenticationService {
|
||||
public string token_hash {
|
||||
get;
|
||||
construct;
|
||||
@@ -795,13 +818,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class NullAuthenticationService : Object, AuthenticationService {
|
||||
public sealed class NullAuthenticationService : Object, AuthenticationService {
|
||||
public async string authenticate (string token, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.INVALID_OPERATION ("Authentication not expected");
|
||||
}
|
||||
}
|
||||
|
||||
public class UnauthorizedHostSession : Object, HostSession {
|
||||
public sealed class UnauthorizedHostSession : Object, HostSession {
|
||||
public async void ping (uint interval_seconds, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
@@ -875,9 +898,17 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
}
|
||||
|
||||
public class UnauthorizedPortalSession : Object, PortalSession {
|
||||
public sealed class UnauthorizedPortalSession : Object, PortalSession {
|
||||
public async void join (HostApplicationInfo app, SpawnStartState current_state,
|
||||
AgentSessionId[] interrupted_sessions, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable, out SpawnStartState next_state) throws Error, IOError {
|
||||
@@ -885,7 +916,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class UnauthorizedBusSession : Object, BusSession {
|
||||
public sealed class UnauthorizedBusSession : Object, BusSession {
|
||||
public async void attach (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
@@ -1083,7 +1114,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class FrontmostQueryOptions : Object {
|
||||
public sealed class FrontmostQueryOptions : Object {
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1113,7 +1144,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class ApplicationQueryOptions : Object {
|
||||
public sealed class ApplicationQueryOptions : Object {
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1171,7 +1202,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessQueryOptions : Object {
|
||||
public sealed class ProcessQueryOptions : Object {
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1313,7 +1344,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class SessionOptions : Object {
|
||||
public sealed class SessionOptions : Object {
|
||||
public Realm realm {
|
||||
get;
|
||||
set;
|
||||
@@ -1502,6 +1533,46 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelId {
|
||||
public string handle;
|
||||
|
||||
public ChannelId (string handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public ChannelId.generate () {
|
||||
this.handle = Uuid.string_random ().replace ("-", "");
|
||||
}
|
||||
|
||||
public static uint hash (ChannelId? id) {
|
||||
return id.handle.hash ();
|
||||
}
|
||||
|
||||
public static bool equal (ChannelId? a, ChannelId? b) {
|
||||
return a.handle == b.handle;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ServiceSessionId {
|
||||
public string handle;
|
||||
|
||||
public ServiceSessionId (string handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public ServiceSessionId.generate () {
|
||||
this.handle = Uuid.string_random ().replace ("-", "");
|
||||
}
|
||||
|
||||
public static uint hash (ServiceSessionId? id) {
|
||||
return id.handle.hash ();
|
||||
}
|
||||
|
||||
public static bool equal (ServiceSessionId? a, ServiceSessionId? b) {
|
||||
return a.handle == b.handle;
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentScriptId {
|
||||
public uint handle;
|
||||
|
||||
@@ -1518,7 +1589,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptOptions : Object {
|
||||
public sealed class ScriptOptions : Object {
|
||||
public string? name {
|
||||
get;
|
||||
set;
|
||||
@@ -1608,7 +1679,7 @@ namespace Frida {
|
||||
SHARED_MEMORY
|
||||
}
|
||||
|
||||
public class SnapshotOptions : Object {
|
||||
public sealed class SnapshotOptions : Object {
|
||||
public string? warmup_script {
|
||||
get;
|
||||
set;
|
||||
@@ -1683,7 +1754,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class PortalOptions : Object {
|
||||
public sealed class PortalOptions : Object {
|
||||
public TlsCertificate? certificate {
|
||||
get;
|
||||
set;
|
||||
@@ -1746,7 +1817,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class PeerOptions : Object {
|
||||
public sealed class PeerOptions : Object {
|
||||
public string? stun_server {
|
||||
get;
|
||||
set;
|
||||
@@ -1807,7 +1878,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class Relay : Object {
|
||||
public sealed class Relay : Object {
|
||||
public string address {
|
||||
get;
|
||||
construct;
|
||||
@@ -2048,7 +2119,8 @@ namespace Frida {
|
||||
public const string AGENT_SESSION = "/re/frida/AgentSession";
|
||||
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
|
||||
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
|
||||
public const string CHILD_SESSION = "/re/frida/ChildSession";
|
||||
public const string CHANNEL = "/re/frida/Channel";
|
||||
public const string SERVICE = "/re/frida/Service";
|
||||
public const string TRANSPORT_BROKER = "/re/frida/TransportBroker";
|
||||
public const string PORTAL_SESSION = "/re/frida/PortalSession";
|
||||
public const string BUS_SESSION = "/re/frida/BusSession";
|
||||
@@ -2061,6 +2133,14 @@ namespace Frida {
|
||||
public static string for_agent_message_sink (AgentSessionId id) {
|
||||
return AGENT_MESSAGE_SINK + "/" + id.handle;
|
||||
}
|
||||
|
||||
public static string for_channel (ChannelId id) {
|
||||
return CHANNEL + "/" + id.handle;
|
||||
}
|
||||
|
||||
public static string for_service_session (ServiceSessionId id) {
|
||||
return SERVICE + "/" + id.handle;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Marshal {
|
||||
|
||||
+53
-240
@@ -97,7 +97,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class EndpointParameters : Object {
|
||||
public sealed class EndpointParameters : Object {
|
||||
public string? address {
|
||||
get;
|
||||
construct;
|
||||
@@ -234,7 +234,7 @@ namespace Frida {
|
||||
return (inet_addr.get_family () == IPV6) ? @"[$host]" : host;
|
||||
}
|
||||
|
||||
public class WebService : Object {
|
||||
public sealed class WebService : Object {
|
||||
public signal void incoming (IOStream connection, SocketAddress remote_address, DynamicInterface? dynamic_iface);
|
||||
|
||||
public EndpointParameters endpoint_params {
|
||||
@@ -548,12 +548,17 @@ namespace Frida {
|
||||
|
||||
IOStream soup_stream = connection.get_io_stream ();
|
||||
|
||||
SocketConnection socket_stream;
|
||||
soup_stream.get ("base-iostream", out socket_stream);
|
||||
IOStream base_iostream;
|
||||
soup_stream.get ("base-iostream", out base_iostream);
|
||||
|
||||
SocketAddress remote_address;
|
||||
SocketAddress? remote_address = null;
|
||||
SocketConnection? sc = base_iostream as SocketConnection;
|
||||
if (sc == null) {
|
||||
IOStream tls_stream = base_iostream;
|
||||
tls_stream.get ("base-io-stream", out sc);
|
||||
}
|
||||
try {
|
||||
remote_address = socket_stream.get_remote_address ();
|
||||
remote_address = sc.get_remote_address ();
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
@@ -852,7 +857,7 @@ namespace Frida {
|
||||
public abstract void start ();
|
||||
}
|
||||
|
||||
public class DynamicInterface : Object {
|
||||
public sealed class DynamicInterface : Object {
|
||||
public string name {
|
||||
get;
|
||||
construct;
|
||||
@@ -870,7 +875,7 @@ namespace Frida {
|
||||
|
||||
public extern static unowned string _version_string ();
|
||||
|
||||
private class WebConnection : IOStream {
|
||||
private sealed class WebConnection : VirtualStream {
|
||||
public signal void websocket_closed ();
|
||||
|
||||
public Soup.WebsocketConnection websocket {
|
||||
@@ -878,37 +883,9 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public override InputStream input_stream {
|
||||
get {
|
||||
return _input_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public override OutputStream output_stream {
|
||||
get {
|
||||
return _output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOCondition pending_io {
|
||||
get {
|
||||
lock (state)
|
||||
return _pending_io;
|
||||
}
|
||||
}
|
||||
|
||||
private WebInputStream _input_stream;
|
||||
private WebOutputStream _output_stream;
|
||||
|
||||
private Soup.WebsocketState state;
|
||||
private IOCondition _pending_io;
|
||||
private ByteArray recv_queue = new ByteArray ();
|
||||
private ByteArray send_queue = new ByteArray ();
|
||||
|
||||
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
|
||||
|
||||
private MainContext main_context;
|
||||
|
||||
public WebConnection (Soup.WebsocketConnection websocket) {
|
||||
Object (websocket: websocket);
|
||||
}
|
||||
@@ -916,14 +893,6 @@ namespace Frida {
|
||||
construct {
|
||||
websocket.max_incoming_payload_size = (256 * 1024) + 1; // XXX: There's an off-by-one error in libsoup
|
||||
|
||||
_input_stream = new WebInputStream (this);
|
||||
_output_stream = new WebOutputStream (this);
|
||||
|
||||
state = websocket.state;
|
||||
_pending_io = (state == OPEN) ? IOCondition.OUT : IOCondition.IN;
|
||||
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
websocket.closed.connect (on_closed);
|
||||
websocket.message.connect (on_message);
|
||||
}
|
||||
@@ -933,51 +902,50 @@ namespace Frida {
|
||||
websocket.closed.disconnect (on_closed);
|
||||
}
|
||||
|
||||
public override bool close (GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void _close () {
|
||||
if (main_context.is_owner ()) {
|
||||
do_close ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
do_close ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
protected override VirtualStream.State query_initial_state () {
|
||||
switch (websocket.state) {
|
||||
case OPEN:
|
||||
case CLOSING:
|
||||
return OPEN;
|
||||
case CLOSED:
|
||||
return CLOSED;
|
||||
}
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
private void do_close () {
|
||||
protected override IOCondition query_events () {
|
||||
IOCondition new_events = 0;
|
||||
|
||||
if (recv_queue.len > 0 || state != OPEN)
|
||||
new_events |= IN;
|
||||
|
||||
if (state == OPEN)
|
||||
new_events |= OUT;
|
||||
|
||||
return new_events;
|
||||
}
|
||||
|
||||
protected override void handle_close () {
|
||||
if (websocket.state != OPEN)
|
||||
return;
|
||||
|
||||
websocket.close (1000, "Closing");
|
||||
}
|
||||
|
||||
public ssize_t recv (uint8[] buffer) throws IOError {
|
||||
ssize_t n;
|
||||
lock (state) {
|
||||
public override ssize_t read (uint8[] buffer) throws IOError {
|
||||
ssize_t n = 0;
|
||||
with_state_lock (() => {
|
||||
n = ssize_t.min (recv_queue.len, buffer.length);
|
||||
if (n > 0) {
|
||||
Memory.copy (buffer, recv_queue.data, n);
|
||||
recv_queue.remove_range (0, (uint) n);
|
||||
|
||||
recompute_pending_io_unlocked ();
|
||||
update_pending_io ();
|
||||
} else {
|
||||
if (state == OPEN)
|
||||
n = -1;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (n == -1)
|
||||
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
|
||||
@@ -985,9 +953,10 @@ namespace Frida {
|
||||
return n;
|
||||
}
|
||||
|
||||
public ssize_t send (uint8[] buffer) {
|
||||
lock (state)
|
||||
public override ssize_t write (uint8[] buffer) {
|
||||
with_state_lock (() => {
|
||||
send_queue.append (buffer);
|
||||
});
|
||||
|
||||
if (main_context.is_owner ()) {
|
||||
process_send_queue ();
|
||||
@@ -1011,190 +980,34 @@ namespace Frida {
|
||||
|
||||
while (true) {
|
||||
uint8[]? chunk = null;
|
||||
lock (state) {
|
||||
with_state_lock (() => {
|
||||
size_t n = size_t.min (send_queue.len, max_message_size);
|
||||
if (n == 0)
|
||||
return;
|
||||
chunk = send_queue.data[0:n];
|
||||
send_queue.remove_range (0, (uint) n);
|
||||
}
|
||||
});
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
websocket.send_binary (chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public void register_source (Source source, IOCondition condition) {
|
||||
lock (state)
|
||||
sources[source] = condition;
|
||||
}
|
||||
|
||||
public void unregister_source (Source source) {
|
||||
lock (state)
|
||||
sources.unset (source);
|
||||
}
|
||||
|
||||
private void on_closed () {
|
||||
lock (state) {
|
||||
state = websocket.state;
|
||||
recompute_pending_io_unlocked ();
|
||||
}
|
||||
with_state_lock (() => {
|
||||
state = CLOSED;
|
||||
update_pending_io ();
|
||||
});
|
||||
|
||||
websocket_closed ();
|
||||
}
|
||||
|
||||
private void on_message (int type, Bytes message) {
|
||||
lock (state) {
|
||||
with_state_lock (() => {
|
||||
recv_queue.append (message.get_data ());
|
||||
recompute_pending_io_unlocked ();
|
||||
}
|
||||
}
|
||||
|
||||
private void recompute_pending_io_unlocked () {
|
||||
IOCondition new_io = 0;
|
||||
if (recv_queue.len > 0 || state != OPEN)
|
||||
new_io |= IN;
|
||||
if (state == OPEN)
|
||||
new_io |= OUT;
|
||||
_pending_io = new_io;
|
||||
|
||||
foreach (var entry in sources.entries) {
|
||||
unowned Source source = entry.key;
|
||||
IOCondition c = entry.value;
|
||||
if ((new_io & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WebInputStream : InputStream, PollableInputStream {
|
||||
public weak WebConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public WebInputStream (WebConnection connection) {
|
||||
Object (connection: connection);
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_readable () {
|
||||
return (connection.pending_io & IOCondition.IN) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new WebIOSource (connection, IOCondition.IN), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
|
||||
return connection.recv (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private class WebOutputStream : OutputStream, PollableOutputStream {
|
||||
public weak WebConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public WebOutputStream (WebConnection connection) {
|
||||
Object (connection: connection);
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_writable () {
|
||||
return (connection.pending_io & IOCondition.OUT) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new WebIOSource (connection, IOCondition.OUT), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
|
||||
return connection.send (buffer);
|
||||
}
|
||||
|
||||
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class WebIOSource : Source {
|
||||
public WebConnection connection;
|
||||
public IOCondition condition;
|
||||
|
||||
public WebIOSource (WebConnection connection, IOCondition condition) {
|
||||
this.connection = connection;
|
||||
this.condition = condition;
|
||||
|
||||
connection.register_source (this, condition);
|
||||
}
|
||||
|
||||
~WebIOSource () {
|
||||
connection.unregister_source (this);
|
||||
}
|
||||
|
||||
protected override bool prepare (out int timeout) {
|
||||
timeout = -1;
|
||||
return (connection.pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool check () {
|
||||
return (connection.pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool dispatch (SourceFunc? callback) {
|
||||
set_ready_time (-1);
|
||||
|
||||
if (callback == null)
|
||||
return Source.REMOVE;
|
||||
|
||||
return callback ();
|
||||
}
|
||||
|
||||
protected static bool closure_callback (Closure closure) {
|
||||
var return_value = Value (typeof (bool));
|
||||
|
||||
closure.invoke (ref return_value, {});
|
||||
|
||||
return return_value.get_boolean ();
|
||||
update_pending_io ();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
namespace Frida {
|
||||
public abstract class VirtualStream : IOStream {
|
||||
public override InputStream input_stream {
|
||||
get {
|
||||
return _input_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public override OutputStream output_stream {
|
||||
get {
|
||||
return _output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOCondition pending_io {
|
||||
get {
|
||||
lock (state)
|
||||
return _pending_io;
|
||||
}
|
||||
}
|
||||
|
||||
protected State state = OPEN;
|
||||
|
||||
private VirtualInputStream _input_stream;
|
||||
private VirtualOutputStream _output_stream;
|
||||
|
||||
private IOCondition _pending_io;
|
||||
|
||||
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
|
||||
|
||||
protected Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
protected MainContext main_context;
|
||||
|
||||
protected enum State {
|
||||
CREATED,
|
||||
OPENING,
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
protected delegate void WorkFunc ();
|
||||
|
||||
construct {
|
||||
_input_stream = new VirtualInputStream (this);
|
||||
_output_stream = new VirtualOutputStream (this);
|
||||
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
state = query_initial_state ();
|
||||
_pending_io = query_events ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
_output_stream.detach ();
|
||||
_input_stream.detach ();
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
protected virtual State query_initial_state () {
|
||||
return OPEN;
|
||||
}
|
||||
|
||||
protected abstract IOCondition query_events ();
|
||||
|
||||
public override bool close (GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void _close () {
|
||||
if (main_context.is_owner ()) {
|
||||
do_close ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
do_close ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private void do_close () {
|
||||
if (state == CLOSED)
|
||||
return;
|
||||
|
||||
handle_close ();
|
||||
}
|
||||
|
||||
protected abstract void handle_close ();
|
||||
|
||||
public virtual void shutdown_read () throws IOError {
|
||||
}
|
||||
|
||||
public virtual void shutdown_write () throws IOError {
|
||||
}
|
||||
|
||||
public abstract ssize_t read (uint8[] buffer) throws IOError;
|
||||
|
||||
public abstract ssize_t write (uint8[] buffer) throws IOError;
|
||||
|
||||
protected void with_state_lock (WorkFunc func) {
|
||||
lock (state)
|
||||
func ();
|
||||
}
|
||||
|
||||
public void register_source (Source source, IOCondition condition) {
|
||||
lock (state)
|
||||
sources[source] = condition | IOCondition.ERR | IOCondition.HUP;
|
||||
}
|
||||
|
||||
public void unregister_source (Source source) {
|
||||
lock (state)
|
||||
sources.unset (source);
|
||||
}
|
||||
|
||||
protected virtual void update_pending_io () {
|
||||
lock (state) {
|
||||
_pending_io = query_events ();
|
||||
|
||||
foreach (var entry in sources.entries) {
|
||||
unowned Source source = entry.key;
|
||||
IOCondition c = entry.value;
|
||||
if ((_pending_io & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
}
|
||||
|
||||
notify_property ("pending-io");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualInputStream : InputStream, PollableInputStream {
|
||||
private weak VirtualStream stream;
|
||||
|
||||
public VirtualInputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_read ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
|
||||
if (!is_readable ()) {
|
||||
bool done = false;
|
||||
var mutex = Mutex ();
|
||||
var cond = Cond ();
|
||||
|
||||
ulong io_handler = stream.notify["pending-io"].connect ((obj, pspec) => {
|
||||
if (is_readable ()) {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
}
|
||||
});
|
||||
ulong cancellation_handler = 0;
|
||||
if (cancellable != null) {
|
||||
cancellation_handler = cancellable.connect (() => {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
});
|
||||
}
|
||||
|
||||
if (!is_readable ()) {
|
||||
mutex.lock ();
|
||||
while (!done)
|
||||
cond.wait (mutex);
|
||||
mutex.unlock ();
|
||||
}
|
||||
|
||||
if (cancellation_handler != 0)
|
||||
cancellable.disconnect (cancellation_handler);
|
||||
stream.disconnect (io_handler);
|
||||
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
|
||||
return stream.read (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_readable () {
|
||||
if (stream == null)
|
||||
return true;
|
||||
return (stream.pending_io & IOCondition.IN) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.IN), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
return stream.read (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualOutputStream : OutputStream, PollableOutputStream {
|
||||
private weak VirtualStream? stream;
|
||||
|
||||
public VirtualOutputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_write ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_writable () {
|
||||
if (stream == null)
|
||||
return false;
|
||||
return (stream.pending_io & IOCondition.OUT) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.OUT), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
throw new IOError.CLOSED ("Virtual is closed");
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class VirtualIOSource : Source {
|
||||
public VirtualStream? stream;
|
||||
public IOCondition condition;
|
||||
|
||||
public VirtualIOSource (VirtualStream stream, IOCondition condition) {
|
||||
this.stream = stream;
|
||||
this.condition = condition;
|
||||
|
||||
if (stream != null)
|
||||
stream.register_source (this, condition);
|
||||
}
|
||||
|
||||
~VirtualIOSource () {
|
||||
if (stream != null)
|
||||
stream.unregister_source (this);
|
||||
}
|
||||
|
||||
protected override bool prepare (out int timeout) {
|
||||
timeout = -1;
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
protected override bool check () {
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
private bool is_ready () {
|
||||
IOCondition pending_io = (stream != null) ? stream.pending_io : IOCondition.IN;
|
||||
return (pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool dispatch (SourceFunc? callback) {
|
||||
set_ready_time (-1);
|
||||
|
||||
if (callback == null)
|
||||
return Source.REMOVE;
|
||||
|
||||
return callback ();
|
||||
}
|
||||
|
||||
protected static bool closure_callback (Closure closure) {
|
||||
var return_value = Value (typeof (bool));
|
||||
|
||||
closure.invoke (ref return_value, {});
|
||||
|
||||
return return_value.get_boolean ();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -38,7 +38,7 @@ namespace Frida {
|
||||
public abstract unowned string get_uuid_value () throws Error;
|
||||
}
|
||||
|
||||
public class VariantReader : Object, ObjectReader {
|
||||
public sealed class VariantReader : Object, ObjectReader {
|
||||
public Variant root_object {
|
||||
get {
|
||||
return scopes.peek_head ().val;
|
||||
@@ -180,7 +180,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonObjectBuilder : Object, ObjectBuilder {
|
||||
public sealed class JsonObjectBuilder : Object, ObjectBuilder {
|
||||
private Json.Builder builder = new Json.Builder ();
|
||||
private Gee.Map<string, Bytes> raw_values = new Gee.HashMap<string, Bytes> ();
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonObjectReader : Object, ObjectReader {
|
||||
public sealed class JsonObjectReader : Object, ObjectReader {
|
||||
private Json.Reader reader;
|
||||
|
||||
public JsonObjectReader (string json) throws Error {
|
||||
|
||||
+26
-2
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class XpcClient : Object {
|
||||
public sealed class XpcClient : Object {
|
||||
public signal void message (Darwin.Xpc.Object obj);
|
||||
|
||||
public State state {
|
||||
@@ -123,7 +123,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class XpcObjectReader {
|
||||
public sealed class XpcObjectReader {
|
||||
public Darwin.Xpc.Object root_object {
|
||||
get {
|
||||
return scopes.peek_head ().object;
|
||||
@@ -136,6 +136,8 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int TranslateErrorFunc (string domain, int code, string description);
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public XpcObjectReader (Darwin.Xpc.Object obj) {
|
||||
@@ -224,6 +226,28 @@ namespace Frida {
|
||||
return error.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
public void check_nserror (TranslateErrorFunc translate_error) throws Error {
|
||||
if (!has_member ("error"))
|
||||
return;
|
||||
|
||||
read_member ("error");
|
||||
|
||||
unowned string domain = read_member ("domain").get_string_value ();
|
||||
end_member ();
|
||||
|
||||
var code = (int) read_member ("code").get_int64_value ();
|
||||
end_member ();
|
||||
|
||||
string description = read_member ("userInfo").read_member ("NSLocalizedDescription").get_string_value ();
|
||||
if (description.has_suffix ("."))
|
||||
description = description[:description.length - 1];
|
||||
|
||||
throw (Error) new GLib.Error.literal (
|
||||
Quark.from_string ("frida-error-quark"),
|
||||
translate_error (domain, code, description),
|
||||
description);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Object get_object_value (Darwin.Xpc.Type expected_type) throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Object> (expected_type);
|
||||
}
|
||||
|
||||
+28
-14
@@ -1,5 +1,5 @@
|
||||
namespace Frida.Gadget {
|
||||
private class Config : Object, Json.Serializable {
|
||||
private sealed class Config : Object, Json.Serializable {
|
||||
public Object interaction {
|
||||
get;
|
||||
set;
|
||||
@@ -93,7 +93,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptInteraction : Object, Json.Serializable {
|
||||
private sealed class ScriptInteraction : Object, Json.Serializable {
|
||||
public string path {
|
||||
get;
|
||||
set;
|
||||
@@ -145,7 +145,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptDirectoryInteraction : Object {
|
||||
private sealed class ScriptDirectoryInteraction : Object {
|
||||
public string path {
|
||||
get;
|
||||
set;
|
||||
@@ -164,7 +164,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptConfig : Object, Json.Serializable {
|
||||
private sealed class ScriptConfig : Object, Json.Serializable {
|
||||
public ProcessFilter? filter {
|
||||
get;
|
||||
set;
|
||||
@@ -216,7 +216,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessFilter : Object {
|
||||
private sealed class ProcessFilter : Object {
|
||||
public string[] executables {
|
||||
get;
|
||||
set;
|
||||
@@ -258,7 +258,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ListenInteraction : SocketInteraction {
|
||||
private sealed class ListenInteraction : SocketInteraction {
|
||||
public PortConflictBehavior on_port_conflict {
|
||||
get;
|
||||
set;
|
||||
@@ -287,7 +287,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectInteraction : SocketInteraction, Json.Serializable {
|
||||
private sealed class ConnectInteraction : SocketInteraction, Json.Serializable {
|
||||
public string[]? acl {
|
||||
get;
|
||||
set;
|
||||
@@ -328,7 +328,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class Location : Object {
|
||||
private sealed class Location : Object {
|
||||
public string executable_name {
|
||||
get;
|
||||
construct;
|
||||
@@ -988,7 +988,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptRunner : BaseController {
|
||||
private sealed class ScriptRunner : BaseController {
|
||||
private ScriptEngine engine;
|
||||
private Script script;
|
||||
|
||||
@@ -1036,7 +1036,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ScriptDirectoryRunner : BaseController {
|
||||
private sealed class ScriptDirectoryRunner : BaseController {
|
||||
public string directory_path {
|
||||
get;
|
||||
construct;
|
||||
@@ -1241,8 +1241,14 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class Script : Object, RpcPeer {
|
||||
private const uint8 QUICKJS_BYTECODE_MAGIC = 0x02;
|
||||
private sealed class Script : Object, RpcPeer {
|
||||
private const uint8 QUICKJS_BYTECODE_MAGIC =
|
||||
#if BIG_ENDIAN
|
||||
0x42
|
||||
#else
|
||||
0x02
|
||||
#endif
|
||||
;
|
||||
|
||||
public enum ChangeBehavior {
|
||||
IGNORE,
|
||||
@@ -1453,7 +1459,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ControlServer : BaseController {
|
||||
private sealed class ControlServer : BaseController {
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
@@ -1927,6 +1933,14 @@ namespace Frida.Gadget {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to inject libraries when embedded");
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open channels when embedded");
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open services when embedded");
|
||||
}
|
||||
|
||||
private void validate_pid (uint pid) throws Error {
|
||||
if (pid != this_process.pid)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to act on other processes when embedded");
|
||||
@@ -1958,7 +1972,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ClusterClient : BaseController {
|
||||
private sealed class ClusterClient : BaseController {
|
||||
public SocketConnectable connectable {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
public class Frida.TunnelInterfaceObserver : Object, DynamicInterfaceObserver {
|
||||
public sealed class Frida.TunnelInterfaceObserver : Object, DynamicInterfaceObserver {
|
||||
#if IOS || TVOS
|
||||
private Gee.Map<string, DynamicInterface> interfaces = new Gee.HashMap<string, DynamicInterface> ();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class ThreadIgnoreScope {
|
||||
public sealed class ThreadIgnoreScope {
|
||||
public enum Kind {
|
||||
APPLICATION_THREAD,
|
||||
FRIDA_THREAD
|
||||
@@ -43,7 +43,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
public class ThreadCountCloaker : Object {
|
||||
public sealed class ThreadCountCloaker : Object {
|
||||
private ReadFunc * read_slot;
|
||||
private static ReadFunc old_read_impl;
|
||||
|
||||
@@ -156,12 +156,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class ThreadCountCloaker : Object {
|
||||
public sealed class ThreadCountCloaker : Object {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LINUX
|
||||
public class ThreadListCloaker : Object, DirListFilter {
|
||||
public sealed class ThreadListCloaker : Object, DirListFilter {
|
||||
private string our_dir_by_pid;
|
||||
private DirListCloaker cloaker;
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class FDListCloaker : Object, DirListFilter {
|
||||
public sealed class FDListCloaker : Object, DirListFilter {
|
||||
private string our_dir_by_pid;
|
||||
private DirListCloaker cloaker;
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class DirListCloaker : Object {
|
||||
private sealed class DirListCloaker : Object {
|
||||
public weak DirListFilter filter {
|
||||
get;
|
||||
construct;
|
||||
@@ -467,10 +467,10 @@ namespace Frida {
|
||||
public abstract bool matches_file (string name);
|
||||
}
|
||||
#else
|
||||
public class ThreadListCloaker : Object {
|
||||
public sealed class ThreadListCloaker : Object {
|
||||
}
|
||||
|
||||
public class FDListCloaker : Object {
|
||||
public sealed class FDListCloaker : Object {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class ExitMonitor : Object, Gum.InvocationListener {
|
||||
public sealed class ExitMonitor : Object, Gum.InvocationListener {
|
||||
public weak ExitHandler handler {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Frida {
|
||||
#if WINDOWS
|
||||
public class FileDescriptorGuard : Object {
|
||||
public sealed class FileDescriptorGuard : Object {
|
||||
public Gum.MemoryRange agent_range {
|
||||
get;
|
||||
construct;
|
||||
@@ -11,7 +11,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class FileDescriptorGuard : Object {
|
||||
public sealed class FileDescriptorGuard : Object {
|
||||
public Gum.MemoryRange agent_range {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Frida {
|
||||
#if WINDOWS
|
||||
public class FileDescriptorTablePadder {
|
||||
public sealed class FileDescriptorTablePadder {
|
||||
public static FileDescriptorTablePadder obtain () {
|
||||
return new FileDescriptorTablePadder ();
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class FileDescriptorTablePadder {
|
||||
public sealed class FileDescriptorTablePadder {
|
||||
private const int MIN_TABLE_SIZE = 32;
|
||||
|
||||
private static unowned FileDescriptorTablePadder shared_instance = null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Frida {
|
||||
#if WINDOWS
|
||||
public class ForkMonitor : Object {
|
||||
public sealed class ForkMonitor : Object {
|
||||
public weak ForkHandler handler {
|
||||
get;
|
||||
construct;
|
||||
@@ -11,7 +11,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class ForkMonitor : Object, Gum.InvocationListener {
|
||||
public sealed class ForkMonitor : Object, Gum.InvocationListener {
|
||||
public weak ForkHandler handler {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class PortalClient : Object, AgentSessionProvider {
|
||||
public sealed class PortalClient : Object, AgentSessionProvider {
|
||||
public signal void resume ();
|
||||
public signal void kill ();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class ScriptEngine : Object {
|
||||
public sealed class ScriptEngine : Object {
|
||||
public signal void message_from_script (AgentScriptId script_id, string json, Bytes? data);
|
||||
public signal void message_from_debugger (AgentScriptId script_id, string message);
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace Frida {
|
||||
source.attach (Gum.ScriptBackend.get_scheduler ().get_js_context ());
|
||||
}
|
||||
|
||||
public class ScriptInstance : Object, RpcPeer {
|
||||
public sealed class ScriptInstance : Object, RpcPeer {
|
||||
public signal void closed ();
|
||||
public signal void message (string json, Bytes? data);
|
||||
public signal void debug_message (string message);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class SpawnMonitor : Object, Gum.InvocationListener {
|
||||
public sealed class SpawnMonitor : Object, Gum.InvocationListener {
|
||||
public weak SpawnHandler handler {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Frida {
|
||||
#if DARWIN
|
||||
public class ThreadSuspendMonitor : Object {
|
||||
public sealed class ThreadSuspendMonitor : Object {
|
||||
public weak ProcessInvader invader {
|
||||
get;
|
||||
construct;
|
||||
@@ -131,7 +131,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class ThreadSuspendMonitor : Object {
|
||||
public sealed class ThreadSuspendMonitor : Object {
|
||||
public weak ProcessInvader invader {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -55,6 +55,7 @@ struct _FridaUnwindHookState
|
||||
gpointer set_info_original;
|
||||
void (* set_info) (gpointer cursor, gint is_return_address);
|
||||
gpointer (* get_reg) (gpointer cursor, gint reg);
|
||||
GumInterceptor * interceptor;
|
||||
};
|
||||
|
||||
#if __has_feature (ptrauth_calls)
|
||||
@@ -97,7 +98,6 @@ _frida_unwind_sitter_hook_libunwind (void)
|
||||
#if GLIB_SIZEOF_VOID_P == 8
|
||||
gpointer * set_info_slot;
|
||||
gpointer get_reg_impl;
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
if (state != NULL)
|
||||
return;
|
||||
@@ -121,8 +121,9 @@ _frida_unwind_sitter_hook_libunwind (void)
|
||||
state->set_info = FRIDA_RESIGN_PTR (state->set_info_original);
|
||||
state->get_reg = FRIDA_RESIGN_PTR (get_reg_impl);
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
if (gum_interceptor_replace (interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
|
||||
state->interceptor = gum_interceptor_obtain ();
|
||||
|
||||
if (gum_interceptor_replace (state->interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
|
||||
!= GUM_REPLACE_OK)
|
||||
goto unsupported_version;
|
||||
|
||||
@@ -137,13 +138,12 @@ unsupported_version:
|
||||
void
|
||||
_frida_unwind_sitter_unhook_libunwind (void)
|
||||
{
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
gum_interceptor_revert (interceptor, state->set_info_original);
|
||||
gum_interceptor_revert (state->interceptor, state->set_info_original);
|
||||
|
||||
g_object_unref (state->interceptor);
|
||||
|
||||
g_slice_free (FridaUnwindHookState, state);
|
||||
state = NULL;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Frida {
|
||||
#if DARWIN
|
||||
public class UnwindSitter : Object {
|
||||
public sealed class UnwindSitter : Object {
|
||||
public weak ProcessInvader invader {
|
||||
get;
|
||||
construct;
|
||||
@@ -62,7 +62,7 @@ namespace Frida {
|
||||
public extern static void _unhook_libunwind ();
|
||||
}
|
||||
#else
|
||||
public class UnwindSitter : Object {
|
||||
public sealed class UnwindSitter : Object {
|
||||
public weak ProcessInvader invader {
|
||||
get;
|
||||
construct;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class PipeTransport : Object {
|
||||
public sealed class PipeTransport : Object {
|
||||
public string local_address {
|
||||
get;
|
||||
construct;
|
||||
@@ -42,7 +42,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
public class WindowsPipe : IOStream {
|
||||
public sealed class WindowsPipe : IOStream {
|
||||
public string address {
|
||||
get;
|
||||
construct;
|
||||
|
||||
+105
-64
@@ -6,6 +6,8 @@ project('frida-core', 'vala', 'c', 'cpp',
|
||||
default_options: ['c_std=gnu99,c99', 'cpp_std=c++17'],
|
||||
)
|
||||
|
||||
fs = import('fs')
|
||||
|
||||
if not meson.get_compiler('vala').version().endswith('-frida')
|
||||
error('''Incompatible Vala compiler detected.
|
||||
|
||||
@@ -20,7 +22,7 @@ cc = meson.get_compiler('c')
|
||||
cpp = meson.get_compiler('cpp')
|
||||
|
||||
releng = meson.global_source_root() / 'releng'
|
||||
if not import('fs').exists(releng)
|
||||
if not fs.exists(releng)
|
||||
releng = meson.project_source_root() / 'releng'
|
||||
endif
|
||||
|
||||
@@ -98,11 +100,6 @@ else
|
||||
host_cpu_mode = '32'
|
||||
endif
|
||||
host_docks_mobile_devices = host_os in ['windows', 'macos', 'linux'] and host_cpu_mode == '64'
|
||||
gumjs_archs = {
|
||||
'x86': 'ia32',
|
||||
'x86_64': 'x64',
|
||||
}
|
||||
host_arch_gumjs = gumjs_archs.get(host_arch, host_arch)
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
c_languages += ['objc', 'objcpp']
|
||||
@@ -162,6 +159,14 @@ if host_os_family == 'darwin'
|
||||
endif
|
||||
endif
|
||||
|
||||
if host_machine.endian() == 'big'
|
||||
if host_abi in ['arm', 'armhf']
|
||||
host_abi = 'armbe8'
|
||||
elif host_abi == 'arm64'
|
||||
host_abi = 'arm64be'
|
||||
endif
|
||||
endif
|
||||
|
||||
if host_os_family == 'darwin' and host_arch == 'arm64'
|
||||
host_is_modern = host_abi == 'arm64e'
|
||||
else
|
||||
@@ -354,6 +359,44 @@ if host_os == 'android'
|
||||
])
|
||||
endif
|
||||
|
||||
if host_toolchain == 'microsoft'
|
||||
lib = find_program('lib')
|
||||
ar = ''
|
||||
nm = ''
|
||||
ranlib = ''
|
||||
strip = ''
|
||||
else
|
||||
lib = ''
|
||||
ar = find_program('ar')
|
||||
nm = find_program('nm')
|
||||
ranlib = find_program('ranlib')
|
||||
strip = find_program('strip')
|
||||
endif
|
||||
if host_os_family != 'windows'
|
||||
if host_os_family == 'darwin'
|
||||
readelf = ''
|
||||
otool = find_program('otool')
|
||||
libtool = find_program('libtool')
|
||||
else
|
||||
readelf = find_program('readelf')
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
else
|
||||
readelf = ''
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
install_name_tool = find_program('install_name_tool')
|
||||
lipo = find_program('lipo')
|
||||
codesign = find_program('codesign')
|
||||
else
|
||||
install_name_tool = ''
|
||||
lipo = ''
|
||||
codesign = ''
|
||||
endif
|
||||
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
have_fruity_backend = get_option('fruity_backend') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
@@ -366,15 +409,54 @@ have_barebone_backend = get_option('barebone_backend') \
|
||||
.disable_if(not quickjs_dep.found()) \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
have_compiler_backend = get_option('compiler_backend') \
|
||||
.disable_if(not have_local_backend, error_message: 'compiler backend requires the local backend for script execution') \
|
||||
.disable_auto_if(host_os == 'watchos') \
|
||||
.allowed()
|
||||
if get_option('compiler_snapshot').auto()
|
||||
# Generating our snapshot with V8's CPU simulator takes a very long time.
|
||||
generate_compiler_snapshot = meson.can_run_host_binaries()
|
||||
else
|
||||
generate_compiler_snapshot = get_option('compiler_snapshot').enabled()
|
||||
compiler_backend_opt = get_option('compiler_backend') \
|
||||
.disable_auto_if(host_os == 'watchos')
|
||||
have_compiler_backend = false
|
||||
if compiler_backend_opt.allowed()
|
||||
go = find_program('go', native: true, required: false)
|
||||
if go.found()
|
||||
message('Detecting cgo availability')
|
||||
detect_output = run_command(
|
||||
[
|
||||
python,
|
||||
files('tools' / 'cgo' / 'detect.py'),
|
||||
go,
|
||||
host_os,
|
||||
host_abi,
|
||||
shlib_suffix,
|
||||
cc.get_id(),
|
||||
'>>>', cc.cmd_array(), '<<<',
|
||||
'>>>', ar, '<<<',
|
||||
'>>>', nm, '<<<',
|
||||
'>>>', ranlib, '<<<',
|
||||
],
|
||||
capture: true,
|
||||
check: true
|
||||
).stdout()
|
||||
lines = detect_output.split('\n')
|
||||
status = lines[0]
|
||||
if status == 'ok'
|
||||
have_compiler_backend = true
|
||||
tokens = lines[1].split(' ')
|
||||
go_mode = tokens[0]
|
||||
go_config = tokens[1]
|
||||
compiler_backend_static_compilation = go_mode == 'c-archive'
|
||||
if compiler_backend_static_compilation
|
||||
vala_flags += '--define=COMPILER_BACKEND_STATIC_COMPILATION'
|
||||
endif
|
||||
else
|
||||
error_message = lines[1]
|
||||
if compiler_backend_opt.enabled()
|
||||
error(error_message)
|
||||
else
|
||||
warning(f'Building without Compiler backend: @error_message@')
|
||||
endif
|
||||
endif
|
||||
elif compiler_backend_opt.enabled()
|
||||
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
|
||||
else
|
||||
warning('Building without Compiler backend: Go >= 1.24 not found')
|
||||
endif
|
||||
endif
|
||||
|
||||
build_gadget = get_option('gadget') \
|
||||
@@ -414,7 +496,7 @@ foreach b : [['local', have_local_backend],
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if have_barebone_backend or have_compiler_backend
|
||||
if host_os_family == 'darwin' or host_os == 'android' or have_barebone_backend or have_compiler_backend
|
||||
node = find_program('node', version: '>=18.0.0', native: true, required: false)
|
||||
if not node.found()
|
||||
error('Need Node.js >= 18.0.0 to process JavaScript code at build time')
|
||||
@@ -431,7 +513,6 @@ if gi_dep.found()
|
||||
else
|
||||
girdir = get_option('prefix') / get_option('datadir') / 'gir-1.0'
|
||||
endif
|
||||
uninstalled_girdir = meson.current_build_dir() / 'src'
|
||||
|
||||
vapidir = get_option('prefix') / get_option('datadir') / 'vala' / 'vapi'
|
||||
|
||||
@@ -445,12 +526,6 @@ gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
|
||||
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
|
||||
if have_v8
|
||||
vala_flags += ['--define=HAVE_V8']
|
||||
v8_mksnapshot = find_program('v8-mksnapshot-@0@-@1@'.format(host_os, host_abi), required: false)
|
||||
if not v8_mksnapshot.found()
|
||||
v8_mksnapshot = ''
|
||||
endif
|
||||
else
|
||||
v8_mksnapshot = ''
|
||||
endif
|
||||
|
||||
backend_deps_private = []
|
||||
@@ -572,42 +647,6 @@ if host_os in ['ios', 'tvos']
|
||||
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
|
||||
endif
|
||||
|
||||
if host_toolchain == 'microsoft'
|
||||
lib = find_program('lib')
|
||||
ar = ''
|
||||
nm = ''
|
||||
strip = ''
|
||||
else
|
||||
lib = ''
|
||||
ar = find_program('ar')
|
||||
nm = find_program('nm')
|
||||
strip = find_program('strip')
|
||||
endif
|
||||
if host_os_family != 'windows'
|
||||
if host_os_family == 'darwin'
|
||||
readelf = ''
|
||||
otool = find_program('otool')
|
||||
libtool = find_program('libtool')
|
||||
else
|
||||
readelf = find_program('readelf')
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
else
|
||||
readelf = ''
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
install_name_tool = find_program('install_name_tool')
|
||||
lipo = find_program('lipo')
|
||||
codesign = find_program('codesign')
|
||||
else
|
||||
install_name_tool = ''
|
||||
lipo = ''
|
||||
codesign = ''
|
||||
endif
|
||||
|
||||
if get_option('devkits').length() != 0
|
||||
if get_option('default_library') != 'static'
|
||||
error('Devkits can only be generated from static libraries')
|
||||
@@ -665,6 +704,8 @@ post_process = [
|
||||
'@INPUT0@',
|
||||
]
|
||||
|
||||
compile_js_agent = files('tools' / 'compile-js-agent.py')
|
||||
|
||||
mapper_opt = get_option('mapper')
|
||||
if mapper_opt.auto()
|
||||
have_mapper = host_os_family == 'darwin'
|
||||
@@ -699,14 +740,15 @@ vala_flags += [
|
||||
'--pkg', 'config',
|
||||
]
|
||||
|
||||
vala_flags += ['--define=' + host_os_family.to_upper()]
|
||||
vala_flags += '--define=' + host_os_family.to_upper()
|
||||
if host_os != host_os_family
|
||||
vala_flags += ['--define=' + host_os.to_upper()]
|
||||
vala_flags += '--define=' + host_os.to_upper()
|
||||
endif
|
||||
vala_flags += ['--define=' + host_arch.to_upper()]
|
||||
vala_flags += '--define=' + host_arch.to_upper()
|
||||
if host_abi != host_arch
|
||||
vala_flags += ['--define=' + host_abi.to_upper()]
|
||||
vala_flags += '--define=' + host_abi.to_upper()
|
||||
endif
|
||||
vala_flags += '--define=' + host_machine.endian().to_upper() + '_ENDIAN'
|
||||
add_project_arguments(vala_flags, language: 'vala')
|
||||
add_project_arguments('-I' + vapidir, language: 'c')
|
||||
|
||||
@@ -751,7 +793,6 @@ summary(
|
||||
'assets': get_option('assets'),
|
||||
'mapper': have_mapper,
|
||||
'connectivity': tls_provider_dep.found(),
|
||||
'compiler_snapshot': generate_compiler_snapshot,
|
||||
'gadget': build_gadget,
|
||||
'server': build_server,
|
||||
'portal': build_portal,
|
||||
|
||||
+1
-7
@@ -37,13 +37,7 @@ option('barebone_backend',
|
||||
option('compiler_backend',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Include frida-compile backend powered by GumJS',
|
||||
)
|
||||
|
||||
option('compiler_snapshot',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Speed up compiler startup by using a snapshot'
|
||||
description: 'Include frida-compile backend powered by ESBuild and typescript-go',
|
||||
)
|
||||
|
||||
option('assets',
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ namespace Frida.Portal {
|
||||
return application.run ();
|
||||
}
|
||||
|
||||
private class Application : Object {
|
||||
private sealed class Application : Object {
|
||||
public signal void ready (bool success);
|
||||
|
||||
public PortalService service {
|
||||
|
||||
+1
-1
Submodule releng updated: 2eecc41cbe...e10baa39a2.
+1
-1
@@ -235,7 +235,7 @@ namespace Frida.Server {
|
||||
}
|
||||
#endif
|
||||
|
||||
private class Application : Object {
|
||||
private sealed class Application : Object {
|
||||
public signal void ready (bool success);
|
||||
|
||||
public string? device_id {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class AgentContainer : Object, AgentSessionProvider {
|
||||
public sealed class AgentContainer : Object, AgentSessionProvider {
|
||||
public DBusConnection connection {
|
||||
get;
|
||||
private set;
|
||||
|
||||
+91284
File diff suppressed because it is too large.
Load diff
+29500
File diff suppressed because it is too large.
Load diff
+141723
File diff suppressed because it is too large.
Load diff
+57
-10
@@ -16,13 +16,21 @@ GIR_NAMESPACES = {
|
||||
"glib": GLIB_NAMESPACE,
|
||||
}
|
||||
|
||||
CORE_TAG_IMPLEMENTS = f"{{{CORE_NAMESPACE}}}implements"
|
||||
CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
|
||||
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
|
||||
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
|
||||
|
||||
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate refined Frida API definitions")
|
||||
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp'], default='bundle')
|
||||
parser.add_argument('frida_version', metavar='frida-version', type=str)
|
||||
parser.add_argument('frida_major_version', metavar='frida-major-version', type=str)
|
||||
parser.add_argument('frida_minor_version', metavar='frida-minor-version', type=str)
|
||||
parser.add_argument('frida_micro_version', metavar='frida-micro-version', type=str)
|
||||
parser.add_argument('frida_nano_version', metavar='frida-nano-version', type=str)
|
||||
parser.add_argument('api_version', metavar='api-version', type=str)
|
||||
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('core_gir', metavar='/path/to/Frida-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
|
||||
@@ -35,6 +43,13 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
output_type = args.output_type
|
||||
frida_version = args.frida_version
|
||||
frida_version_components = (
|
||||
args.frida_major_version,
|
||||
args.frida_minor_version,
|
||||
args.frida_micro_version,
|
||||
args.frida_nano_version,
|
||||
)
|
||||
api_version = args.api_version
|
||||
core_header = args.core_header.read()
|
||||
core_gir = args.core_gir.read()
|
||||
@@ -50,6 +65,7 @@ def main():
|
||||
|
||||
toplevel_names = [
|
||||
"frida.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
"file-monitor.vala",
|
||||
@@ -75,7 +91,7 @@ def main():
|
||||
elif output_type == 'vapi':
|
||||
enable_vapi = True
|
||||
|
||||
api = parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi)
|
||||
api = parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi)
|
||||
|
||||
if enable_header:
|
||||
emit_header(api, output_dir)
|
||||
@@ -90,7 +106,26 @@ def emit_header(api, output_dir):
|
||||
with OutputFile(output_dir / 'frida-core.h') as output_header_file:
|
||||
output_header_file.write("#ifndef __FRIDA_CORE_H__\n#define __FRIDA_CORE_H__\n\n")
|
||||
|
||||
output_header_file.write("#include <glib.h>\n#include <glib-object.h>\n#include <gio/gio.h>\n#include <json-glib/json-glib.h>\n")
|
||||
output_header_file.write("#include <glib.h>\n#include <glib-object.h>\n#include <gio/gio.h>\n#include <json-glib/json-glib.h>\n\n")
|
||||
|
||||
output_header_file.write(f"#define FRIDA_VERSION \"{api.frida_version}\"\n\n")
|
||||
|
||||
for name, value in zip(['MAJOR', 'MINOR', 'MICRO', 'NANO'], api.frida_version_components):
|
||||
output_header_file.write(f"#define FRIDA_{name}_VERSION {value}\n")
|
||||
|
||||
output_header_file.write("""
|
||||
#define FRIDA_CHECK_VERSION(maj, min, mic) \\
|
||||
(FRIDA_CURRENT_VERSION >= FRIDA_VERSION_ENCODE ((maj), (min), (mic)))
|
||||
|
||||
#define FRIDA_CURRENT_VERSION \\
|
||||
FRIDA_VERSION_ENCODE ( \\
|
||||
FRIDA_MAJOR_VERSION, \\
|
||||
FRIDA_MINOR_VERSION, \\
|
||||
FRIDA_MICRO_VERSION)
|
||||
|
||||
#define FRIDA_VERSION_ENCODE(maj, min, mic) \\
|
||||
(((maj) * 1000000U) + ((min) * 1000U) + (mic))
|
||||
""")
|
||||
|
||||
output_header_file.write("\nG_BEGIN_DECLS\n")
|
||||
|
||||
@@ -203,7 +238,9 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
for elem in core_elements + base_elements:
|
||||
if tag_name == "class":
|
||||
for child in list(elem):
|
||||
if child.tag == CORE_TAG_FIELD or child.get("name").startswith("_"):
|
||||
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") == "FridaBase.AgentMessageSink") \
|
||||
or child.tag == CORE_TAG_FIELD \
|
||||
or child.get("name").startswith("_"):
|
||||
elem.remove(child)
|
||||
merged_namespace.append(elem)
|
||||
|
||||
@@ -215,6 +252,7 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
result = ET.tostring(merged_root,
|
||||
encoding="unicode",
|
||||
xml_declaration=True)
|
||||
result = result.replace("FridaBase.", "Frida.")
|
||||
with OutputFile(output_dir / f"Frida-{api.version}.gir") as output_gir:
|
||||
output_gir.write(result)
|
||||
|
||||
@@ -265,7 +303,7 @@ def emit_vapi(api, output_dir):
|
||||
output_deps_file.write("gobject-2.0\n")
|
||||
output_deps_file.write("gio-2.0\n")
|
||||
|
||||
def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
|
||||
def parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
|
||||
all_headers = core_header + "\n" + base_header
|
||||
|
||||
all_enum_names = [m.group(1) for m in re.finditer(r"^\t+public\s+enum\s+(\w+)\s+", toplevel_code + "\n" + base_vapi, re.MULTILINE)]
|
||||
@@ -288,12 +326,18 @@ def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, b
|
||||
"StaticAuthenticationService": "AuthenticationService",
|
||||
}
|
||||
internal_type_prefixes = [
|
||||
"AgentMessageKind",
|
||||
"Fruity",
|
||||
"HostSession",
|
||||
"MessageType",
|
||||
"PeerSetup",
|
||||
"PortConflictBehavior",
|
||||
"ResultCode",
|
||||
"SpawnStartState",
|
||||
"State",
|
||||
"StringTerminator",
|
||||
"UnloadPolicy",
|
||||
"WebService",
|
||||
"Winjector"
|
||||
]
|
||||
seen_enum_names = set()
|
||||
@@ -375,7 +419,7 @@ def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, b
|
||||
if delegate_cname not in seen_cdelegates:
|
||||
seen_cdelegates.add(delegate_cname)
|
||||
object_type.c_delegate_typedefs.append(beautify_cprototype(d.group(0)))
|
||||
if object_type.kind == 'interface' and object_type.name != "Injector":
|
||||
if object_type.kind == 'interface':
|
||||
for m in re.finditer("^(struct _" + object_type.c_name + "Iface {[^}]+};)$", all_headers, re.MULTILINE):
|
||||
object_type.c_iface_definition = beautify_cinterface(m.group(1))
|
||||
|
||||
@@ -412,8 +456,8 @@ def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, b
|
||||
current_enum.vapi_declaration = stripped_line
|
||||
else:
|
||||
ignoring = True
|
||||
elif stripped_line.startswith("public class") or stripped_line.startswith("public interface"):
|
||||
name = re.match(r"^public (class|interface) (\w+) ", stripped_line).group(2)
|
||||
elif (match := OBJECT_TYPE_PATTERN.match(stripped_line)) is not None:
|
||||
name = match.group(3)
|
||||
if name not in object_type_by_name:
|
||||
ignoring = True
|
||||
else:
|
||||
@@ -452,7 +496,7 @@ def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, b
|
||||
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
|
||||
f.c_prototype = beautify_cprototype(m.group(0))
|
||||
|
||||
return ApiSpec(api_version, object_types, functions, enum_types, error_types)
|
||||
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
|
||||
|
||||
def function_is_public(name):
|
||||
return not name.startswith("_") and \
|
||||
@@ -466,17 +510,20 @@ def function_is_public(name):
|
||||
"parse_control_address",
|
||||
"parse_cluster_address",
|
||||
"parse_socket_address",
|
||||
"negotiate_connection"
|
||||
"negotiate_connection",
|
||||
"check_kernel_version",
|
||||
]
|
||||
|
||||
def parse_vala_object_types(source) -> List[ApiObjectType]:
|
||||
return [ApiObjectType(m.group(2), m.group(1)) for m in re.finditer(r"^\t+public\s+(class|interface)\s+(\w+)\s+", source, re.MULTILINE)]
|
||||
return [ApiObjectType(m.group(3), m.group(2)) for m in OBJECT_TYPE_PATTERN.finditer(source, re.MULTILINE)]
|
||||
|
||||
def parse_vapi_functions(vapi) -> List[ApiFunction]:
|
||||
return [ApiFunction(m.group(1), m.group(0)) for m in re.finditer(r"^\tpublic static .+ (\w+) \(.+;", vapi, re.MULTILINE)]
|
||||
|
||||
@dataclass
|
||||
class ApiSpec:
|
||||
frida_version: str
|
||||
frida_version_components: List[int]
|
||||
version: str
|
||||
object_types: List[ApiObjectType]
|
||||
functions: List[ApiFunction]
|
||||
|
||||
+14
-3
@@ -1,6 +1,13 @@
|
||||
fs = import('fs')
|
||||
|
||||
generate_api = [python, files('generate.py')]
|
||||
|
||||
generate_api_args = [
|
||||
frida_version,
|
||||
frida_major_version.to_string(),
|
||||
frida_minor_version.to_string(),
|
||||
frida_micro_version.to_string(),
|
||||
frida_nano_version.to_string(),
|
||||
api_version,
|
||||
core_build_dir / 'frida-core.h',
|
||||
core_build_dir / core_gir_name,
|
||||
@@ -32,6 +39,11 @@ core_public_h = core_api[0]
|
||||
core_public_gir = core_api[1]
|
||||
core_public_vapi = [core_api[2], core_api[3]]
|
||||
|
||||
install_glib_girs = not gi_dep.found() and not fs.exists(girdir / 'GLib-2.0.gir')
|
||||
glib_gir = fs.copyfile('GLib-2.0.gir', install: install_glib_girs, install_dir: girdir)
|
||||
gobject_gir = fs.copyfile('GObject-2.0.gir', install: install_glib_girs, install_dir: girdir)
|
||||
gio_gir = fs.copyfile('Gio-2.0.gir', install: install_glib_girs, install_dir: girdir)
|
||||
|
||||
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
|
||||
input: core_public_vapi,
|
||||
output: f'frida-core-@[email protected]',
|
||||
@@ -59,8 +71,6 @@ core_public_lib = custom_target('frida-core-library',
|
||||
install_dir: get_option('libdir'),
|
||||
)
|
||||
|
||||
fs = import('fs')
|
||||
|
||||
core_public_extra_headers = []
|
||||
if host_os == 'android'
|
||||
core_public_extra_headers += fs.copyfile(frida_selinux_h)
|
||||
@@ -71,6 +81,7 @@ if build_machine.system() == 'windows'
|
||||
else
|
||||
uninstalled_incdir = meson.current_build_dir()
|
||||
endif
|
||||
uninstalled_girdir = uninstalled_incdir
|
||||
uninstalled_vapidir = uninstalled_incdir
|
||||
|
||||
uninstalled_variables = {
|
||||
@@ -86,7 +97,7 @@ installed_variables = {
|
||||
}
|
||||
|
||||
core_public_dep = declare_dependency(
|
||||
sources: [core_public_h, core_public_gir, core_public_vapi_stamp, core_public_extra_headers],
|
||||
sources: [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp, core_public_extra_headers],
|
||||
include_directories: include_directories('.'),
|
||||
link_args: backend_libs_private,
|
||||
link_with: core_public_lib,
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Frida.Barebone {
|
||||
public abstract async void deallocate (Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
public class SimpleAllocator : Object, Allocator {
|
||||
public sealed class SimpleAllocator : Object, Allocator {
|
||||
public size_t page_size {
|
||||
get { return _page_size; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class ArmMachine : Object, Machine {
|
||||
public sealed class ArmMachine : Object, Machine {
|
||||
public override GDB.Client gdb {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class Arm64Machine : Object, Machine {
|
||||
public sealed class Arm64Machine : Object, Machine {
|
||||
public override GDB.Client gdb {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class UnknownMachine : Object, Machine {
|
||||
public sealed class UnknownMachine : Object, Machine {
|
||||
public GDB.Client gdb {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class IA32Machine : Object, Machine {
|
||||
public sealed class IA32Machine : Object, Machine {
|
||||
public override GDB.Client gdb {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class X64Machine : Object, Machine {
|
||||
public sealed class X64Machine : Object, Machine {
|
||||
public override GDB.Client gdb {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
public class BareboneHostSessionBackend : Object, HostSessionBackend {
|
||||
public sealed class BareboneHostSessionBackend : Object, HostSessionBackend {
|
||||
private BareboneHostSessionProvider? provider;
|
||||
|
||||
private const uint16 DEFAULT_PORT = 3333;
|
||||
@@ -30,7 +30,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class BareboneHostSessionProvider : Object, HostSessionProvider {
|
||||
public sealed class BareboneHostSessionProvider : Object, HostSessionProvider {
|
||||
public string id {
|
||||
get { return "barebone"; }
|
||||
}
|
||||
@@ -141,12 +141,35 @@ namespace Frida {
|
||||
return yield this.host_session.link_agent_session (id, sink, cancellable);
|
||||
}
|
||||
|
||||
public void unlink_agent_session (HostSession host_session, AgentSessionId id) {
|
||||
if (host_session != this.host_session)
|
||||
return;
|
||||
|
||||
this.host_session.unlink_agent_session (id);
|
||||
}
|
||||
|
||||
public async IOStream link_channel (HostSession host_session, ChannelId id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Channels are not supported by this backend");
|
||||
}
|
||||
|
||||
public void unlink_channel (HostSession host_session, ChannelId id) {
|
||||
}
|
||||
|
||||
public async ServiceSession link_service_session (HostSession host_session, ServiceSessionId id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
|
||||
}
|
||||
|
||||
public void unlink_service_session (HostSession host_session, ServiceSessionId id) {
|
||||
}
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
agent_session_detached (id, reason, crash);
|
||||
}
|
||||
}
|
||||
|
||||
public class BareboneHostSession : Object, HostSession {
|
||||
public sealed class BareboneHostSession : Object, HostSession {
|
||||
public Barebone.Services services {
|
||||
get;
|
||||
construct;
|
||||
@@ -260,6 +283,14 @@ namespace Frida {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void unlink_agent_session (AgentSessionId id) {
|
||||
BareboneAgentSession? session = agent_sessions[id];
|
||||
if (session == null)
|
||||
return;
|
||||
|
||||
session.message_sink = null;
|
||||
}
|
||||
|
||||
public async InjectorPayloadId inject_library_file (uint pid, string path, string entrypoint, string data,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
@@ -270,6 +301,14 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Channels are not supported by this backend");
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
|
||||
}
|
||||
|
||||
private void on_agent_session_closed (BareboneAgentSession session) {
|
||||
AgentSessionId id = session.id;
|
||||
|
||||
@@ -282,7 +321,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class BareboneAgentSession : Object, AgentSession {
|
||||
private sealed class BareboneAgentSession : Object, AgentSession {
|
||||
public signal void closed ();
|
||||
|
||||
public AgentSessionId id {
|
||||
@@ -426,7 +465,8 @@ namespace Frida {
|
||||
|
||||
public async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
get_script (script_id).load ();
|
||||
var script = get_script (script_id);
|
||||
yield script.load (cancellable);
|
||||
}
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -525,7 +565,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
namespace Barebone {
|
||||
public class Services : Object {
|
||||
public sealed class Services : Object {
|
||||
public Machine machine {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class Callback : Object {
|
||||
public sealed class Callback : Object {
|
||||
private uint64 code;
|
||||
private CallbackHandler handler;
|
||||
private Machine machine;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def main(argv):
|
||||
output_dir, priv_dir, input_dir, npm = [Path(d).resolve() for d in argv[1:]]
|
||||
|
||||
try:
|
||||
generate_runtime(output_dir, priv_dir, input_dir, npm)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_runtime(output_dir, priv_dir, input_dir, npm):
|
||||
priv_dir.mkdir(exist_ok=True)
|
||||
|
||||
shutil.copy(input_dir / "package.json", priv_dir)
|
||||
shutil.copy(input_dir / "package-lock.json", priv_dir)
|
||||
|
||||
runtime_reldir = Path("script-runtime")
|
||||
runtime_srcdir = input_dir / runtime_reldir
|
||||
runtime_intdir = priv_dir / runtime_reldir
|
||||
if runtime_intdir.exists():
|
||||
shutil.rmtree(runtime_intdir)
|
||||
shutil.copytree(runtime_srcdir, runtime_intdir)
|
||||
|
||||
subprocess.run([npm, "install"], capture_output=True, cwd=priv_dir, check=True)
|
||||
|
||||
shutil.copy(priv_dir / "script-runtime.js", output_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public class Interceptor : Object {
|
||||
public sealed class Interceptor : Object {
|
||||
public Machine machine {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -29,11 +29,11 @@ barebone_script_runtime = custom_target('frida-barebone-script-runtime',
|
||||
],
|
||||
command: [
|
||||
python,
|
||||
files('generate-script-runtime.py'),
|
||||
meson.current_build_dir(),
|
||||
compile_js_agent,
|
||||
'@OUTPUT@',
|
||||
'@PRIVATE_DIR@',
|
||||
meson.current_source_dir(),
|
||||
npm,
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
backend_sources += custom_target('frida-data-barebone',
|
||||
|
||||
Generated
+451
-442
@@ -1,494 +1,503 @@
|
||||
{
|
||||
"name": "frida-barebone-runtime",
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frida-barebone-runtime",
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^18.3.2",
|
||||
"frida-compile": "^16.2.1"
|
||||
"@types/frida-gum": "^19.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/assert": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/assert/-/assert-3.0.2.tgz",
|
||||
"integrity": "sha512-JXJq5SbXGrM5EkjrZKfRmB29zOoEOix02NC6A5TSJ+C1GE/X051EinJJsuOO2pEOx7KZwpvAHvS0WXW0+levKg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/base64-js": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/base64-js/-/base64-js-2.0.3.tgz",
|
||||
"integrity": "sha512-2w0F+1TynOTCZ/v7du9LdHPWwq0lJhazjo2fF9upMyQmA1zHetT14fLuQ1v/6T0qPgyeEGkiSrybstU8EsgeUA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/@frida/buffer": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz",
|
||||
"integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/crosspath": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/crosspath/-/crosspath-3.0.0.tgz",
|
||||
"integrity": "sha512-bNdO1spIPD2P40XtK89N49oZpJhstdlnkJZcD4yJ17jrdkm9Ctu0sd9MIEX6Z8Tm8ydhVJBAOMEKl9/R27onAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^17.0.36"
|
||||
},
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.9.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/diagnostics_channel": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/diagnostics_channel/-/diagnostics_channel-1.0.0.tgz",
|
||||
"integrity": "sha512-mYX1jp/5Bpk24tHArJNx65iCk7qSuV8YJkdU0gFNVtJUXxfV8BG5WuPa4mL+ynxsbWWpsg/cwKZbLAepYKTdQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/events": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@frida/events/-/events-4.0.4.tgz",
|
||||
"integrity": "sha512-qJVQ6VWHf9sjUKuiJzoCAC00frbpcwxeYfvQ+PP9LU/d70j+QvjWgYe98Qa3ekLaBU6r/AvWm8ThKCDUCLWrQQ==",
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/http": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/http/-/http-4.0.2.tgz",
|
||||
"integrity": "sha512-cvkc7ex7GmVXVOWqtjXKBWUUbYEBgpNRKZbEEoMeI8KiIs8zejKwg+N7rx7296Ao+EP3+xcUr4wBVr3xLaUVfQ==",
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"http-parser-js": "^0.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/http-parser-js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/http-parser-js/-/http-parser-js-1.0.0.tgz",
|
||||
"integrity": "sha512-2nMrNXt/OeTlWbqnE8AH4Sfz4I2+BGoN206dzKEyC/g2svtn83Xu+zuv/V3TkwrA27s26Mcy84ZwsXeNlqNxUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/https": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/https/-/https-1.0.0.tgz",
|
||||
"integrity": "sha512-OiqQ6qsALcWOktRLq07oJ0i6sH8eX6MXb/MdZS1qVKDRf6wchH4Pjn6fiLB+pt/OlYbggk+DOfpHwSdjTwuHMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/ieee754": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/ieee754/-/ieee754-2.0.2.tgz",
|
||||
"integrity": "sha512-wlcUebnne4ENN7GDr5pTH598ZDLMVOsh0FjenxeVOe6u7ewZkz9gGRnLnZKJAm9kl5G6XhdxhI0cSXVQK/rQUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/@frida/net": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/net/-/net-4.0.2.tgz",
|
||||
"integrity": "sha512-qQRe7hQ+ZfCcG/SE3P1TRqQ9bmuK/T7wPCYaT4z56rBPWAxsaQbQHpX4fR6OrFaSDr7X0xJLsTbdIp9hGhhLZg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/os": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/os/-/os-1.0.2.tgz",
|
||||
"integrity": "sha512-3ISAiGNiyIya3QN2EHBCz1wqP0enTdSxP99wUeroeh8+AQRmgoOr/5TRnrVry8pe378anay3fmV/tdUMMSkehQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/path": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/path/-/path-2.0.3.tgz",
|
||||
"integrity": "sha512-2RQy36QatoC846fzBhBhV8sXMsSOBGoYvwTHeaE1zUdz7F4RNScP4QEekTTooBYWYX/XjiF36KQpYAzc9OYFtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/process": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@frida/process/-/process-1.2.1.tgz",
|
||||
"integrity": "sha512-nvCu22DstFW2ttGFtOKekHM7vnjbZm+XgtvavOt427GNT6uV7k0JYK9tnMbcLMRWv57DG6udAmuJlWs8Paq1ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/punycode": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/punycode/-/punycode-3.0.0.tgz",
|
||||
"integrity": "sha512-XVSDY2KamDs1D5/fTVgHcOSNxdU4kTboxzqJMBbTjcQC7XScIT9c0EfbwKCq7Kci6gWQdsHSCr7lU+9Oc4KAdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/querystring": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/querystring/-/querystring-1.0.0.tgz",
|
||||
"integrity": "sha512-15m1fOZPmoO/vWlgPJrG/J9/BJDz6a2/JpVGpS8ynNzo+fBhTznaStX5nHxUs24mVTqh/OqLo0EiYJM5WWHXxg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/readable-stream": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/readable-stream/-/readable-stream-4.1.3.tgz",
|
||||
"integrity": "sha512-ntGUFmi+CryRGRJIK13a/VST2Ad19uivbln8Xd92vKPAARq+6vMIASDyZIqyl5BLRccfiyCHdYgrgQ6RI5rUig==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/reserved-words": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/reserved-words/-/reserved-words-1.0.0.tgz",
|
||||
"integrity": "sha512-2yG/XxJlsGlk/mm6eZTb4OAaQEhkTI2qaFfZFtAsrA/XuCpuMWkS4y/guyBlsRu4hAuhK2HPmNM8+OLLK1zM9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/stream": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/stream/-/stream-1.0.2.tgz",
|
||||
"integrity": "sha512-4OuaC1ztmEKgTq3WeBhsy8Oq+AwW9n9cYnvLklcC9jwD93AEwgbWpecLlxJCVuALvTMdhKPg0nQVfyGYP/i9Bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@frida/readable-stream": "^4.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/string_decoder": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/string_decoder/-/string_decoder-2.0.0.tgz",
|
||||
"integrity": "sha512-in371tYZMHQiW9HF5MS3JDw6Ao6tyBoq34UWy2rzOswYyMG1rpizh85ofi/yVkxDiaqybEZefxzkVittpPGT6g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/terser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/terser/-/terser-1.0.0.tgz",
|
||||
"integrity": "sha512-59h9WuNzD1Rx/zwoWqQ/FW/4Y/Q3R91Eng2hEwdHapqiTDvtKbZ08F6CynCR7ZVinrh4tLYsF46AtVPTz1ys9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/timers": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/timers/-/timers-3.0.0.tgz",
|
||||
"integrity": "sha512-3b+0igv10aT8TMxefrTAd06rActqbxJLY2Xkkq9vYcPBffB/yHszl0NYIp/5ko8WC3ecDYPU6bQiY6fjs72zTA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/tty": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/tty/-/tty-1.0.0.tgz",
|
||||
"integrity": "sha512-p/kjLnKYxEAB1MdYP8+5rKv9CsHzyA+0jg9BcGETzjQVKHHcroHDULRxDYUh+DC7qs6cpX8QdDQh9E+a6ydgsQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/url": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/url/-/url-1.0.2.tgz",
|
||||
"integrity": "sha512-ZKunbKJHMr8w2Eb/5K1avy0MzK1B998S17wYXNv3RmzBGxMm8S5T0F3qEpRxkU7/72P8m4izyQU87fWl+FjQsQ==",
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/util": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/util/-/util-1.0.3.tgz",
|
||||
"integrity": "sha512-htcG3uDiRXv89ERVNNYhfase39kJ2X75ZARfrYcYEtJLFEsSk0nemM1YnEIR4CjrHvdvkWHrwgKkS+acOyoNEg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/vm": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/vm/-/vm-2.0.0.tgz",
|
||||
"integrity": "sha512-7fsjLWscZT5odNIBtg6qbLNI+vAk1xmii6H5W2kaYkMYt0vRohQEcDSUWacA+eaWlu5SvMjZI82Yibj/3G9pJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
|
||||
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "18.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.3.2.tgz",
|
||||
"integrity": "sha512-bso8bJRxrhchNRrOMmT18st/THIdjzxFdcMtJ/gGSYSknJeerQ8ArBwBtlWt/f3pqVVs1Mz1Fg6lvUdjXswa+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.0.tgz",
|
||||
"integrity": "sha512-qViK1KwvPEAkajwSUv0wpZvZLiX8xO4JFFCh8dfYZfzSyOEfoE0KliCP/nJSKIKG6N3hXnPowrvPufjs5fLKAA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-compile": {
|
||||
"version": "16.2.1",
|
||||
"resolved": "https://registry.npmjs.org/frida-compile/-/frida-compile-16.2.1.tgz",
|
||||
"integrity": "sha512-be/U5h8l0cb0b+fWeZclhfw1s2xER8fPOjnTU4rZF8oh5GkB0BeAEaApwfGrdmeZkVNdUhDLXud7DqEljHRWmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@frida/assert": "^3.0.1",
|
||||
"@frida/base64-js": "^2.0.3",
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"@frida/crosspath": "^3.0.0",
|
||||
"@frida/diagnostics_channel": "^1.0.0",
|
||||
"@frida/events": "^4.0.4",
|
||||
"@frida/http": "^4.0.2",
|
||||
"@frida/http-parser-js": "^1.0.0",
|
||||
"@frida/https": "^1.0.0",
|
||||
"@frida/ieee754": "^2.0.2",
|
||||
"@frida/net": "^4.0.1",
|
||||
"@frida/os": "^1.0.0",
|
||||
"@frida/path": "^2.0.3",
|
||||
"@frida/process": "^1.2.1",
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0",
|
||||
"@frida/readable-stream": "^4.1.3",
|
||||
"@frida/reserved-words": "^1.0.0",
|
||||
"@frida/stream": "^1.0.2",
|
||||
"@frida/string_decoder": "^2.0.0",
|
||||
"@frida/terser": "^1.0.0",
|
||||
"@frida/timers": "^3.0.0",
|
||||
"@frida/tty": "^1.0.0",
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"commander": "^9.4.0",
|
||||
"frida-fs": "^5.2.3",
|
||||
"typed-emitter": "^2.1.0"
|
||||
"node": ">=18"
|
||||
},
|
||||
"bin": {
|
||||
"frida-compile": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-fs": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-5.2.3.tgz",
|
||||
"integrity": "sha512-jJhtNI86UqDkOtB72XR7Ia9KHudQUxIFIbZadS9kxrDU6Io1jLhoMNp++iW7n7MaDgFQLRR8VgNj9VViGzaydA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-parser-js": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
|
||||
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
|
||||
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
|
||||
"dev": true,
|
||||
"optionalDependencies": {
|
||||
"rxjs": "*"
|
||||
"@esbuild/aix-ppc64": "0.25.5",
|
||||
"@esbuild/android-arm": "0.25.5",
|
||||
"@esbuild/android-arm64": "0.25.5",
|
||||
"@esbuild/android-x64": "0.25.5",
|
||||
"@esbuild/darwin-arm64": "0.25.5",
|
||||
"@esbuild/darwin-x64": "0.25.5",
|
||||
"@esbuild/freebsd-arm64": "0.25.5",
|
||||
"@esbuild/freebsd-x64": "0.25.5",
|
||||
"@esbuild/linux-arm": "0.25.5",
|
||||
"@esbuild/linux-arm64": "0.25.5",
|
||||
"@esbuild/linux-ia32": "0.25.5",
|
||||
"@esbuild/linux-loong64": "0.25.5",
|
||||
"@esbuild/linux-mips64el": "0.25.5",
|
||||
"@esbuild/linux-ppc64": "0.25.5",
|
||||
"@esbuild/linux-riscv64": "0.25.5",
|
||||
"@esbuild/linux-s390x": "0.25.5",
|
||||
"@esbuild/linux-x64": "0.25.5",
|
||||
"@esbuild/netbsd-arm64": "0.25.5",
|
||||
"@esbuild/netbsd-x64": "0.25.5",
|
||||
"@esbuild/openbsd-arm64": "0.25.5",
|
||||
"@esbuild/openbsd-x64": "0.25.5",
|
||||
"@esbuild/sunos-x64": "0.25.5",
|
||||
"@esbuild/win32-arm64": "0.25.5",
|
||||
"@esbuild/win32-ia32": "0.25.5",
|
||||
"@esbuild/win32-x64": "0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"description": "Script runtime used by Frida's Barebone backend",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"build": "frida-compile script-runtime/entrypoint.ts -o script-runtime.js -c"
|
||||
"build": "esbuild script-runtime/entrypoint.ts --outfile=script-runtime.js --bundle --platform=node --target=es2022 --minify",
|
||||
"check": "tsc",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^18.3.2",
|
||||
"frida-compile": "^16.2.1"
|
||||
"@types/frida-gum": "^19.0.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
private class RustModule : Object {
|
||||
private sealed class RustModule : Object {
|
||||
public signal void console_output (string message);
|
||||
|
||||
public Gee.List<Export> exports {
|
||||
@@ -208,7 +208,10 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
~CompilationAssets () {
|
||||
rmtree (workdir);
|
||||
try {
|
||||
FS.rmtree (workdir);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static string make_main_rs (string code, Machine machine) {
|
||||
@@ -428,23 +431,4 @@ namespace Frida.Barebone {
|
||||
yield file.replace_contents_async (content.data, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void rmtree (File dir) {
|
||||
try {
|
||||
var enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS);
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child);
|
||||
else
|
||||
child.delete ();
|
||||
}
|
||||
|
||||
dir.delete ();
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,24 +210,6 @@ Object.defineProperties(globalThis, {
|
||||
}
|
||||
}
|
||||
},
|
||||
ObjC: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
Swift: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
Java: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
console: {
|
||||
enumerable: true,
|
||||
value: new Console()
|
||||
@@ -278,6 +260,8 @@ Object.defineProperties(Process, {
|
||||
base: NULL,
|
||||
size: 0x1000,
|
||||
path: "/kernel",
|
||||
ensureInitialized() {
|
||||
},
|
||||
enumerateImports() {
|
||||
return [];
|
||||
},
|
||||
@@ -290,11 +274,23 @@ Object.defineProperties(Process, {
|
||||
enumerateRanges() {
|
||||
return [];
|
||||
},
|
||||
enumerateSections() {
|
||||
return [];
|
||||
},
|
||||
enumerateDependencies() {
|
||||
return [];
|
||||
},
|
||||
findExportByName() {
|
||||
return null;
|
||||
},
|
||||
getExportByName(exportName) {
|
||||
throw new Error(`unable to find export '${exportName}'`);
|
||||
getExportByName(name) {
|
||||
throw new Error(`unable to find export '${name}'`);
|
||||
},
|
||||
findSymbolByName() {
|
||||
return null;
|
||||
},
|
||||
getSymbolByName(name) {
|
||||
throw new Error(`unable to find symbol '${name}'`);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
+163
-39
@@ -1,5 +1,5 @@
|
||||
namespace Frida {
|
||||
private class BareboneScript : Object {
|
||||
private sealed class BareboneScript : Object {
|
||||
public signal void message (string json, Bytes? data);
|
||||
|
||||
public AgentScriptId id {
|
||||
@@ -72,7 +72,7 @@ namespace Frida {
|
||||
private QuickJS.Value int64_func = QuickJS.Undefined;
|
||||
private QuickJS.Value uint64_func = QuickJS.Undefined;
|
||||
|
||||
private Gee.ArrayList<QuickJS.Value?> entrypoints = new Gee.ArrayList<QuickJS.Value?> ();
|
||||
private Gee.Queue<Entrypoint> entrypoints = new Gee.ArrayQueue<Entrypoint> ();
|
||||
private Gee.Map<string, Asset> assets = new Gee.HashMap<string, Asset> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
@@ -328,6 +328,8 @@ namespace Frida {
|
||||
foreach (var val in values)
|
||||
ctx.free_value (val);
|
||||
|
||||
entrypoints.clear ();
|
||||
|
||||
QuickJS.Atom atoms[] = {
|
||||
address_key,
|
||||
base_key,
|
||||
@@ -376,48 +378,65 @@ namespace Frida {
|
||||
yield;
|
||||
}
|
||||
|
||||
public void load () {
|
||||
foreach (QuickJS.Value? entrypoint in entrypoints) {
|
||||
var result = ctx.eval_function (entrypoint);
|
||||
if (result.is_exception ())
|
||||
public async void load (Cancellable? cancellable) throws IOError {
|
||||
Entrypoint? entrypoint;
|
||||
while ((entrypoint = entrypoints.poll ()) != null) {
|
||||
var result = ctx.eval_function (ctx.dup_value (entrypoint.callable));
|
||||
if (result.is_exception ()) {
|
||||
catch_and_emit ();
|
||||
ctx.free_value (result);
|
||||
|
||||
if (runtime_obj.is_undefined ()) {
|
||||
runtime_obj = global.get_property_str (ctx, "$rt");
|
||||
if (!runtime_obj.is_undefined ()) {
|
||||
dispatch_exception_func = runtime_obj.get_property_str (ctx, "dispatchException");
|
||||
assert (!dispatch_exception_func.is_undefined ());
|
||||
|
||||
dispatch_message_func = runtime_obj.get_property_str (ctx, "dispatchMessage");
|
||||
assert (!dispatch_message_func.is_undefined ());
|
||||
|
||||
var native_pointer_instance = global.get_property_str (ctx, "NULL");
|
||||
assert (!native_pointer_instance.is_undefined ());
|
||||
var native_pointer_proto = native_pointer_instance.get_prototype (ctx);
|
||||
|
||||
var ir_proto = ctx.make_object_proto (native_pointer_proto);
|
||||
add_cfunc (ir_proto, "replace", on_invocation_retval_replace, 1);
|
||||
ctx.set_class_proto (invocation_retval_class, ir_proto);
|
||||
|
||||
ctx.free_value (native_pointer_proto);
|
||||
ctx.free_value (native_pointer_instance);
|
||||
|
||||
ptr_func = global.get_property_str (ctx, "ptr");
|
||||
assert (!ptr_func.is_undefined ());
|
||||
|
||||
int64_func = global.get_property_str (ctx, "int64");
|
||||
assert (!int64_func.is_undefined ());
|
||||
|
||||
uint64_func = global.get_property_str (ctx, "uint64");
|
||||
assert (!uint64_func.is_undefined ());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entrypoint.kind == ESM) {
|
||||
var op = new PromiseWaitOperation (this, result);
|
||||
var r = yield op.perform (cancellable);
|
||||
if (!r.error.is_null ())
|
||||
on_unhandled_exception (r.error);
|
||||
} else {
|
||||
ctx.free_value (result);
|
||||
}
|
||||
|
||||
maybe_bind_runtime ();
|
||||
}
|
||||
|
||||
perform_pending_io ();
|
||||
}
|
||||
|
||||
private void maybe_bind_runtime () {
|
||||
if (!runtime_obj.is_undefined ())
|
||||
return;
|
||||
|
||||
runtime_obj = global.get_property_str (ctx, "$rt");
|
||||
if (runtime_obj.is_undefined ())
|
||||
return;
|
||||
|
||||
dispatch_exception_func = runtime_obj.get_property_str (ctx, "dispatchException");
|
||||
assert (!dispatch_exception_func.is_undefined ());
|
||||
|
||||
dispatch_message_func = runtime_obj.get_property_str (ctx, "dispatchMessage");
|
||||
assert (!dispatch_message_func.is_undefined ());
|
||||
|
||||
var native_pointer_instance = global.get_property_str (ctx, "NULL");
|
||||
assert (!native_pointer_instance.is_undefined ());
|
||||
var native_pointer_proto = native_pointer_instance.get_prototype (ctx);
|
||||
|
||||
var ir_proto = ctx.make_object_proto (native_pointer_proto);
|
||||
add_cfunc (ir_proto, "replace", on_invocation_retval_replace, 1);
|
||||
ctx.set_class_proto (invocation_retval_class, ir_proto);
|
||||
|
||||
ctx.free_value (native_pointer_proto);
|
||||
ctx.free_value (native_pointer_instance);
|
||||
|
||||
ptr_func = global.get_property_str (ctx, "ptr");
|
||||
assert (!ptr_func.is_undefined ());
|
||||
|
||||
int64_func = global.get_property_str (ctx, "int64");
|
||||
assert (!int64_func.is_undefined ());
|
||||
|
||||
uint64_func = global.get_property_str (ctx, "uint64");
|
||||
assert (!uint64_func.is_undefined ());
|
||||
}
|
||||
|
||||
public void post (string json, Bytes? data) {
|
||||
var json_val = ctx.make_string (json);
|
||||
var data_val = (data != null) ? ctx.make_array_buffer (data.get_data ()) : QuickJS.Null;
|
||||
@@ -495,7 +514,7 @@ namespace Frida {
|
||||
throw_malformed_package ();
|
||||
|
||||
var val = compile_module (entrypoint);
|
||||
entrypoints.add (val);
|
||||
entrypoints.offer (new Entrypoint (this, val, ESM));
|
||||
|
||||
string rest = raw_assets[assets_offset:];
|
||||
if (rest.has_prefix (delimiter_marker))
|
||||
@@ -507,7 +526,7 @@ namespace Frida {
|
||||
}
|
||||
} else {
|
||||
var val = compile_script (source, name);
|
||||
entrypoints.add (val);
|
||||
entrypoints.offer (new Entrypoint (this, val, PLAIN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,6 +727,111 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class Entrypoint {
|
||||
private weak BareboneScript script;
|
||||
public QuickJS.Value callable;
|
||||
public Kind kind;
|
||||
|
||||
public enum Kind {
|
||||
PLAIN,
|
||||
ESM
|
||||
}
|
||||
|
||||
public Entrypoint (BareboneScript script, QuickJS.Value callable, Kind kind) {
|
||||
this.script = script;
|
||||
this.callable = callable;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
~Entrypoint () {
|
||||
script.ctx.free_value (callable);
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseWaitOperation {
|
||||
private BareboneScript script;
|
||||
private QuickJS.Value promise;
|
||||
|
||||
private enum Magic {
|
||||
ON_SUCCESS,
|
||||
ON_FAILURE,
|
||||
}
|
||||
|
||||
public PromiseWaitOperation (BareboneScript script, QuickJS.Value promise) {
|
||||
this.script = script;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
~PromiseWaitOperation () {
|
||||
script.ctx.free_value (promise);
|
||||
}
|
||||
|
||||
public async Result perform (Cancellable? cancellable) throws IOError {
|
||||
var res = new Result (script, perform.callback);
|
||||
|
||||
unowned QuickJS.Context ctx = script.ctx;
|
||||
|
||||
var data = ctx.make_object ();
|
||||
data.set_opaque (res);
|
||||
|
||||
var on_success = ctx.make_cfunction_data (on_settled, 1, Magic.ON_SUCCESS, { data });
|
||||
var on_failure = ctx.make_cfunction_data (on_settled, 1, Magic.ON_FAILURE, { data });
|
||||
|
||||
var then_func = promise.get_property_str (ctx, "then");
|
||||
var catch_func = promise.get_property_str (ctx, "catch");
|
||||
|
||||
ctx.free_value (then_func.call (ctx, promise, { on_success }));
|
||||
ctx.free_value (catch_func.call (ctx, promise, { on_failure }));
|
||||
|
||||
ctx.free_value (catch_func);
|
||||
ctx.free_value (then_func);
|
||||
|
||||
ctx.free_value (on_failure);
|
||||
ctx.free_value (on_success);
|
||||
|
||||
script.perform_pending_io ();
|
||||
yield;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_settled (QuickJS.Context ctx, QuickJS.Value this_val, QuickJS.Value[] argv, int magic,
|
||||
QuickJS.Value[] data) {
|
||||
QuickJS.ClassID cid;
|
||||
unowned Result res = (Result) data[0].get_any_opaque (out cid);
|
||||
|
||||
if (magic == Magic.ON_SUCCESS)
|
||||
res.val = ctx.dup_value (argv[0]);
|
||||
else
|
||||
res.error = ctx.dup_value (argv[0]);
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) res.on_complete);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
|
||||
return QuickJS.Undefined;
|
||||
}
|
||||
|
||||
public class Result {
|
||||
public BareboneScript script;
|
||||
public SourceFunc on_complete;
|
||||
|
||||
public QuickJS.Value val = QuickJS.Null;
|
||||
public QuickJS.Value error = QuickJS.Null;
|
||||
|
||||
public Result (BareboneScript script, owned SourceFunc on_complete) {
|
||||
this.script = script;
|
||||
this.on_complete = (owned) on_complete;
|
||||
}
|
||||
|
||||
~Result () {
|
||||
unowned QuickJS.Context ctx = script.ctx;
|
||||
ctx.free_value (val);
|
||||
ctx.free_value (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeCallbackHandler : Object, Barebone.CallbackHandler {
|
||||
public uint arity {
|
||||
get { return _arity; }
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "Node16",
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["script-runtime/**/*.ts"]
|
||||
}
|
||||
@@ -1,469 +0,0 @@
|
||||
import * as crosspath from "@frida/crosspath";
|
||||
import type * as backend from "frida-compile";
|
||||
import {
|
||||
makeDefaultCompilerOptions,
|
||||
build as _build,
|
||||
watch as _watch,
|
||||
queryDefaultAssets as _queryDefaultAssets,
|
||||
} from "frida-compile";
|
||||
import ts from "frida-compile/ext/typescript.js";
|
||||
import fridaFs from "frida-fs";
|
||||
import fs from "fs";
|
||||
|
||||
const { DiagnosticCategory, FileWatcherEventKind } = ts;
|
||||
|
||||
const { S_IFDIR } = fs.constants;
|
||||
|
||||
export const compilerRoot = "/frida-compile";
|
||||
export const compilerNodeModules = compilerRoot + "/node_modules";
|
||||
|
||||
export const agentDirectories = new Set<string>(__agentDirectories__);
|
||||
export const agentFiles = new Map<string, string>(__agentFiles__);
|
||||
const cachedSourceFiles = new Map<string, ts.SourceFile>();
|
||||
|
||||
declare const __agentDirectories__: string[];
|
||||
declare const __agentFiles__: [string, string][];
|
||||
|
||||
const pendingDiagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
const fileWatchers = new Map<WatcherId, WatcherCallback>();
|
||||
let nextWatcherId: WatcherId = 1;
|
||||
|
||||
populateSourceFileCache();
|
||||
|
||||
function populateSourceFileCache() {
|
||||
const config = makeDefaultCompilerOptions();
|
||||
for (const [name, contents] of agentFiles) {
|
||||
if (name.endsWith(".d.ts")) {
|
||||
const sf = ts.createSourceFile(name, contents, config.target!);
|
||||
ts.prebindSourceFile(sf, config);
|
||||
cachedSourceFiles.set(name, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function init(): void {
|
||||
function onChange(message: WatchChangeMessage): void {
|
||||
const callback = fileWatchers.get(message.id);
|
||||
callback?.(fileWatcherEventKindFromFileState(message.state));
|
||||
|
||||
recv("watch:change", onChange);
|
||||
}
|
||||
recv("watch:change", onChange);
|
||||
|
||||
const origCreateCompilerHostFromProgramHost = (ts as any).createCompilerHostFromProgramHost;
|
||||
(ts as any).createCompilerHostFromProgramHost = (...args: any[]): ts.CompilerHost => {
|
||||
const host = origCreateCompilerHostFromProgramHost(...args);
|
||||
patchCompilerHost(host);
|
||||
return host;
|
||||
};
|
||||
}
|
||||
|
||||
export function build(projectRoot: string, entrypoint: string, sourceMaps: backend.SourceMaps, compression: backend.Compression): string {
|
||||
const system = new FridaSystem(projectRoot);
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
|
||||
try {
|
||||
return _build({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
onCompilerHostCreated: patchCompilerHost,
|
||||
});
|
||||
} finally {
|
||||
flushDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
export function watch(projectRoot: string, entrypoint: string, sourceMaps: backend.SourceMaps, compression: backend.Compression): void {
|
||||
const system = new FridaSystem(projectRoot);
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
_watch({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
})
|
||||
.on("compilationStarting", () => {
|
||||
send(["watch:compilation-starting"]);
|
||||
})
|
||||
.on("compilationFinished", () => {
|
||||
send(["watch:compilation-finished"]);
|
||||
})
|
||||
.on("bundleUpdated", bundle => {
|
||||
send(["watch:bundle-updated", bundle]);
|
||||
});
|
||||
}
|
||||
|
||||
function patchCompilerHost(host: ts.CompilerHost) {
|
||||
const origGetSourceFile = host.getSourceFile;
|
||||
host.getSourceFile = (fileName, ...args) => {
|
||||
const compilerPath = nativePathToCompilerPath(fileName);
|
||||
if (compilerPath !== null) {
|
||||
const sf = cachedSourceFiles.get(compilerPath);
|
||||
if (sf !== undefined) {
|
||||
return sf;
|
||||
}
|
||||
} else {
|
||||
const startPos = fileName.indexOf("/node_modules/@types/");
|
||||
if (startPos !== -1) {
|
||||
const builtinsPath = fileName.substring(startPos);
|
||||
const sf = cachedSourceFiles.get(builtinsPath);
|
||||
if (sf !== undefined) {
|
||||
let allowCache = true;
|
||||
|
||||
if (builtinsPath.startsWith("/node_modules/@types/frida-gum/")) {
|
||||
try {
|
||||
const hashOnDisk = Checksum.compute("sha256", File.readAllText(fileName));
|
||||
const hashInCache = Checksum.compute("sha256", agentFiles.get(sf.fileName)!);
|
||||
allowCache = hashOnDisk === hashInCache;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (allowCache) {
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return origGetSourceFile(fileName, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
export { _build, _watch, _queryDefaultAssets };
|
||||
|
||||
type DiagnosticFile = [path: string, line: number, character: number];
|
||||
|
||||
function onDiagnostic(diagnostic: ts.Diagnostic): void {
|
||||
pendingDiagnostics.push(diagnostic);
|
||||
if (pendingDiagnostics.length === 1) {
|
||||
Script.nextTick(flushDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
function flushDiagnostics(): void {
|
||||
const pending = pendingDiagnostics.splice(0, pendingDiagnostics.length);
|
||||
if (pending.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
send([
|
||||
"diagnostics",
|
||||
pending.map(({
|
||||
category,
|
||||
code,
|
||||
file: tsFile,
|
||||
start,
|
||||
messageText
|
||||
}) => {
|
||||
let file: DiagnosticFile | null = null;
|
||||
if (tsFile !== undefined) {
|
||||
const { line, character } = ts.getLineAndCharacterOfPosition(tsFile, start!);
|
||||
file = [tsFile.fileName, line, character];
|
||||
}
|
||||
return [
|
||||
diagnosticCategoryToString(category),
|
||||
code,
|
||||
file,
|
||||
ts.flattenDiagnosticMessageText(messageText, "\n")
|
||||
];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function diagnosticCategoryToString(category: ts.DiagnosticCategory): string {
|
||||
switch (category) {
|
||||
case DiagnosticCategory.Warning:
|
||||
return "warning";
|
||||
case DiagnosticCategory.Error:
|
||||
return "error";
|
||||
case DiagnosticCategory.Suggestion:
|
||||
return "suggestion";
|
||||
case DiagnosticCategory.Message:
|
||||
return "message";
|
||||
}
|
||||
}
|
||||
|
||||
class FridaSystem implements ts.System {
|
||||
args = [];
|
||||
newLine = "\n";
|
||||
useCaseSensitiveFileNames = true;
|
||||
|
||||
#projectRoot: string;
|
||||
#projectNodeModules: string;
|
||||
|
||||
constructor(projectRoot: string) {
|
||||
this.#projectRoot = crosspath.ensurePosix(projectRoot);
|
||||
this.#projectNodeModules = crosspath.join(this.#projectRoot, "node_modules");
|
||||
}
|
||||
|
||||
write(s: string): void {
|
||||
}
|
||||
|
||||
writeOutputIsTTY(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(path: string, encoding?: string): string | undefined {
|
||||
let result: string | undefined;
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
result = File.readAllText(path);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (result === undefined) {
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
result = agentFiles.get(agentZipPath);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getFileSize(path: string): number {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
File.writeAllText(path, data);
|
||||
}
|
||||
|
||||
watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
return makeDummyWatcher();
|
||||
}
|
||||
|
||||
return makeFileWatcher(path, "file", pollingInterval ?? 0, kind => {
|
||||
callback(path, kind);
|
||||
});
|
||||
}
|
||||
|
||||
watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
return makeDummyWatcher();
|
||||
}
|
||||
|
||||
return makeFileWatcher(path, "directory", 0, kind => {
|
||||
callback(path);
|
||||
});
|
||||
}
|
||||
|
||||
resolvePath(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
const st = fs.statSync(path);
|
||||
return !st.isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath === null) {
|
||||
return false;
|
||||
}
|
||||
return agentFiles.has(agentZipPath);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
if (path === compilerNodeModules) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
const st = fs.statSync(path);
|
||||
return st.isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath === null) {
|
||||
return false;
|
||||
}
|
||||
return agentDirectories.has(agentZipPath);
|
||||
}
|
||||
|
||||
createDirectory(path: string): void {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
getExecutingFilePath(): string {
|
||||
return [compilerRoot, "ext", "typescript.js"].join("/");
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return this.#projectRoot;
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
const result = new Set<string>();
|
||||
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
for (const entry of fridaFs.list(path).filter(entry => entry.type === S_IFDIR)) {
|
||||
result.add(entry.name);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
for (const dir of agentDirectories) {
|
||||
const slashIndex = dir.lastIndexOf("/");
|
||||
const parent = dir.substring(0, slashIndex);
|
||||
if (parent === agentZipPath) {
|
||||
const basename = dir.substring(slashIndex + 1);
|
||||
result.add(basename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(result);
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getModifiedTime(path: string): Date | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setModifiedTime(path: string, time: Date): void {
|
||||
}
|
||||
|
||||
deleteFile(path: string): void {
|
||||
}
|
||||
|
||||
createHash(data: string): string {
|
||||
return this.createSHA256Hash(data);
|
||||
}
|
||||
|
||||
createSHA256Hash(data: string): string {
|
||||
return Checksum.compute("sha256", data);
|
||||
}
|
||||
|
||||
getMemoryUsage(): number {
|
||||
return Frida.heapSize;
|
||||
}
|
||||
|
||||
exit(exitCode?: number): void {
|
||||
}
|
||||
|
||||
realpath(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
getEnvironmentVariable(name: string): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
|
||||
return setTimeout(callback);
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId: any): void {
|
||||
return clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
clearScreen(): void {
|
||||
}
|
||||
|
||||
base64decode(input: string): string {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
base64encode(input: string): string {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
#nativePathToAgentZipPath(path: string): string | null {
|
||||
const compilerPath = nativePathToCompilerPath(path);
|
||||
if (compilerPath !== null) {
|
||||
return compilerPath;
|
||||
}
|
||||
|
||||
if (path.startsWith(this.#projectNodeModules)) {
|
||||
return "/node_modules" + path.substring(this.#projectNodeModules.length);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function nativePathToCompilerPath(path: string): string | null {
|
||||
if (!path.startsWith(compilerRoot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subPath = path.substring(compilerRoot.length);
|
||||
if (subPath.startsWith("/node_modules")) {
|
||||
return subPath;
|
||||
}
|
||||
|
||||
return "/node_modules/frida-compile" + subPath;
|
||||
}
|
||||
|
||||
type WatcherId = number;
|
||||
type WatcherCallback = (eventKind: ts.FileWatcherEventKind) => void;
|
||||
type FileState = "pristine" | "modified" | "deleted";
|
||||
|
||||
interface WatchChangeMessage {
|
||||
id: WatcherId;
|
||||
state: FileState;
|
||||
}
|
||||
|
||||
function makeFileWatcher(path: string, type: "file" | "directory", pollingInterval: number, callback: WatcherCallback): ts.FileWatcher {
|
||||
if (nextWatcherId === 0xffffffff) {
|
||||
nextWatcherId = 1;
|
||||
}
|
||||
let id: WatcherId = nextWatcherId++;
|
||||
while (fileWatchers.has(id)) {
|
||||
id = nextWatcherId++;
|
||||
}
|
||||
|
||||
fileWatchers.set(id, callback);
|
||||
|
||||
send(["watch:add", id, path, type, pollingInterval ?? 0]);
|
||||
|
||||
return {
|
||||
close() {
|
||||
send(["watch:remove", id]);
|
||||
|
||||
fileWatchers.delete(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function makeDummyWatcher(): ts.FileWatcher {
|
||||
return {
|
||||
close() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function fileWatcherEventKindFromFileState(state: FileState): ts.FileWatcherEventKind {
|
||||
switch (state) {
|
||||
case "pristine": return FileWatcherEventKind.Created;
|
||||
case "modified": return FileWatcherEventKind.Changed;
|
||||
case "deleted": return FileWatcherEventKind.Deleted;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const { init, build, watch } = FridaCompilerAgentCore;
|
||||
|
||||
init();
|
||||
|
||||
rpc.exports = { build, watch };
|
||||
@@ -1,222 +0,0 @@
|
||||
const {
|
||||
compilerRoot,
|
||||
compilerNodeModules,
|
||||
agentDirectories,
|
||||
agentFiles,
|
||||
_build,
|
||||
_queryDefaultAssets,
|
||||
} = FridaCompilerAgentCore;
|
||||
|
||||
const projectRoot = '/agent';
|
||||
const projectNodeModules = '/agent/node_modules';
|
||||
const entrypoint = 'index.ts';
|
||||
const sourceMaps = 'included';
|
||||
const compression = 'none';
|
||||
|
||||
const hashes = new Map();
|
||||
let nextHashId = 1;
|
||||
|
||||
function main() {
|
||||
const system = new StubSystem();
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
_build({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
});
|
||||
}
|
||||
|
||||
function onDiagnostic(diagnostic) {
|
||||
throwNotImplemented('diagnostic', ts.flattenDiagnosticMessageText(diagnostic, '\n'));
|
||||
}
|
||||
|
||||
class StubSystem {
|
||||
args = [];
|
||||
newLine = '\n';
|
||||
useCaseSensitiveFileNames = true;
|
||||
|
||||
write(s) {
|
||||
}
|
||||
|
||||
writeOutputIsTTY() {
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(path, encoding) {
|
||||
if (path === '/agent/tsconfig.json')
|
||||
return '{}';
|
||||
|
||||
if (path === '/agent/index.ts')
|
||||
return 'Interceptor.attach(ptr(1234), { onEnter(args) {} });'
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
return agentFiles.get(agentZipPath);
|
||||
}
|
||||
|
||||
throwNotImplemented('readFile', path);
|
||||
}
|
||||
|
||||
getFileSize(path) {
|
||||
throwNotImplemented('getFileSize');
|
||||
}
|
||||
|
||||
writeFile(path, data, writeByteOrderMark) {
|
||||
throwNotImplemented('writeFile');
|
||||
}
|
||||
|
||||
watchFile(path, callback, pollingInterval, options) {
|
||||
throwNotImplemented('watchFile');
|
||||
}
|
||||
|
||||
watchDirectory(path, callback, recursive, options) {
|
||||
throwNotImplemented('watchDirectory');
|
||||
}
|
||||
|
||||
resolvePath(path) {
|
||||
throwNotImplemented('resolvePath');
|
||||
}
|
||||
|
||||
fileExists(path) {
|
||||
if (path === '/package.json' || path === '/agent/package.json')
|
||||
return false;
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null)
|
||||
return agentFiles.has(agentZipPath);
|
||||
|
||||
throwNotImplemented('fileExists', path);
|
||||
}
|
||||
|
||||
directoryExists(path) {
|
||||
if (path === '/' || path === projectRoot || path === compilerNodeModules)
|
||||
return true;
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null)
|
||||
return agentDirectories.has(agentZipPath);
|
||||
|
||||
if (path === '/node_modules/@types' || path === '/node_modules')
|
||||
return false;
|
||||
|
||||
throwNotImplemented('directoryExists', path);
|
||||
}
|
||||
|
||||
createDirectory(path) {
|
||||
throwNotImplemented('createDirectory');
|
||||
}
|
||||
|
||||
getExecutingFilePath() {
|
||||
return [compilerRoot, 'ext', 'typescript.js'].join('/');
|
||||
}
|
||||
|
||||
getCurrentDirectory() {
|
||||
return '/';
|
||||
}
|
||||
|
||||
getDirectories(path) {
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
const result = [];
|
||||
for (const dir of agentDirectories) {
|
||||
const slashIndex = dir.lastIndexOf('/');
|
||||
const parent = dir.substring(0, slashIndex);
|
||||
if (parent === agentZipPath) {
|
||||
const basename = dir.substring(slashIndex + 1);
|
||||
result.push(basename);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throwNotImplemented('getDirectories', path);
|
||||
}
|
||||
|
||||
readDirectory(path, extensions, exclude, include, depth) {
|
||||
if (!extensions.includes('.ts'))
|
||||
throwNotImplemented('readDirectory', path, extensions);
|
||||
return ['index.ts'];
|
||||
}
|
||||
|
||||
getModifiedTime(path) {
|
||||
}
|
||||
|
||||
setModifiedTime(path, time) {
|
||||
}
|
||||
|
||||
deleteFile(path) {
|
||||
}
|
||||
|
||||
createHash(data) {
|
||||
let hash = hashes.get(data);
|
||||
if (hash === undefined) {
|
||||
hash = 'hash' + nextHashId++;
|
||||
hashes.set(data, hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
createSHA256Hash(data) {
|
||||
throwNotImplemented('createSHA256Hash');
|
||||
}
|
||||
|
||||
getMemoryUsage() {
|
||||
throwNotImplemented('getMemoryUsage');
|
||||
}
|
||||
|
||||
exit(exitCode) {
|
||||
}
|
||||
|
||||
realpath(path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
getEnvironmentVariable(name) {
|
||||
return '';
|
||||
}
|
||||
|
||||
setTimeout(callback, ms, ...args) {
|
||||
throwNotImplemented('setTimeout');
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId) {
|
||||
throwNotImplemented('clearTimeout');
|
||||
}
|
||||
|
||||
clearScreen() {
|
||||
}
|
||||
|
||||
base64decode(input) {
|
||||
throwNotImplemented('base64decode');
|
||||
}
|
||||
|
||||
base64encode(input) {
|
||||
throwNotImplemented('base64encode');
|
||||
}
|
||||
|
||||
#nativePathToAgentZipPath(path) {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
const subPath = path.substring(compilerRoot.length);
|
||||
if (subPath.startsWith('/node_modules')) {
|
||||
return subPath;
|
||||
}
|
||||
return '/node_modules/frida-compile' + subPath;
|
||||
}
|
||||
|
||||
if (path.startsWith(projectNodeModules)) {
|
||||
return '/node_modules' + path.substring(projectNodeModules.length);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function throwNotImplemented(operation, ...details) {
|
||||
throw new Error('not implemented: ' + operation + ((details.length > 0) ? ` (${details.join(' ')})` : ''));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
# define FRIDA_CGO_INIT_FUNC _st0_arm64_windows_lib
|
||||
#elif GLIB_SIZEOF_VOID_P == 8
|
||||
# define FRIDA_CGO_INIT_FUNC _st0_amd64_windows_lib
|
||||
#else
|
||||
# define FRIDA_CGO_INIT_FUNC st0_386_windows_lib
|
||||
#endif
|
||||
|
||||
extern void FRIDA_CGO_INIT_FUNC ();
|
||||
|
||||
void
|
||||
_frida_compiler_backend_init_go_runtime (void)
|
||||
{
|
||||
FRIDA_CGO_INIT_FUNC ();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
_frida_compiler_backend_init_go_runtime (void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,816 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
FRIDA_OUTPUT_UNESCAPED,
|
||||
FRIDA_OUTPUT_HEX_BYTES,
|
||||
FRIDA_OUTPUT_C_STRING,
|
||||
} FridaOutputFormat;
|
||||
|
||||
typedef enum {
|
||||
FRIDA_BUNDLE_ESM,
|
||||
FRIDA_BUNDLE_IIFE,
|
||||
} FridaBundleFormat;
|
||||
|
||||
typedef void (* FridaBuildCompleteFunc) (char * bundle, char * error_message, void * user_data);
|
||||
typedef void (* FridaWatchReadyFunc) (uintptr_t session_handle, char * error_message, void * user_data);
|
||||
typedef void (* FridaStartingFunc) (void * user_data);
|
||||
typedef void (* FridaFinishedFunc) (void * user_data);
|
||||
typedef void (* FridaOutputFunc) (char * bundle, void * user_data);
|
||||
typedef void (* FridaDiagnosticFunc) (char * category, int code, char * path, int line, int character, char * text,
|
||||
void * user_data);
|
||||
typedef void (* FridaDestroyFunc) (void * user_data);
|
||||
|
||||
static inline void
|
||||
invoke_build_complete_func (FridaBuildCompleteFunc fn,
|
||||
char * bundle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_watch_ready_func (FridaWatchReadyFunc fn,
|
||||
uintptr_t session_handle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (session_handle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_starting_func (FridaStartingFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_finished_func (FridaFinishedFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_output_func (FridaOutputFunc fn,
|
||||
char * bundle,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_diagnostic_func (FridaDiagnosticFunc fn,
|
||||
char * category,
|
||||
int code,
|
||||
char * path,
|
||||
int line,
|
||||
int character,
|
||||
char * text,
|
||||
void * user_data)
|
||||
{
|
||||
fn (category, code, path, line, character, text, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_destroy_func (FridaDestroyFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
tsscanner "github.com/frida/typescript-go/pkg/scanner"
|
||||
)
|
||||
|
||||
type BuildOptions struct {
|
||||
ProjectRoot string
|
||||
Entrypoint string
|
||||
OutputFormat OutputFormat
|
||||
BundleFormat BundleFormat
|
||||
DisableTypeCheck bool
|
||||
SourceMap bool
|
||||
Compress bool
|
||||
}
|
||||
|
||||
type OutputFormat C.FridaOutputFormat
|
||||
|
||||
const (
|
||||
OutputFormatUnescaped OutputFormat = OutputFormat(C.FRIDA_OUTPUT_UNESCAPED)
|
||||
OutputFormatHexBytes OutputFormat = OutputFormat(C.FRIDA_OUTPUT_HEX_BYTES)
|
||||
OutputFormatCString OutputFormat = OutputFormat(C.FRIDA_OUTPUT_C_STRING)
|
||||
)
|
||||
|
||||
type BundleFormat C.FridaBundleFormat
|
||||
|
||||
const (
|
||||
BundleFormatESM BundleFormat = BundleFormat(C.FRIDA_BUNDLE_ESM)
|
||||
BundleFormatIIFE BundleFormat = BundleFormat(C.FRIDA_BUNDLE_IIFE)
|
||||
)
|
||||
|
||||
type Diagnostic struct {
|
||||
category string
|
||||
code int
|
||||
path string
|
||||
line, character int
|
||||
text string
|
||||
}
|
||||
|
||||
type BuildEventCallbacks struct {
|
||||
OnStart BuildStartCallback
|
||||
OnEnd BuildEndCallback
|
||||
OnOutput BuildOutputCallback
|
||||
OnDiagnostic BuildDiagnosticCallback
|
||||
OnConfigChange ConfigChangeCallback
|
||||
}
|
||||
|
||||
type BuildStartCallback func()
|
||||
type BuildEndCallback func()
|
||||
type BuildOutputCallback func(bundle string)
|
||||
type BuildDiagnosticCallback func(d Diagnostic)
|
||||
|
||||
//export _frida_compiler_backend_build
|
||||
func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr,
|
||||
onCompleteFn C.FridaBuildCompleteFunc, onCompleteData unsafe.Pointer, onCompleteDataDestroy C.FridaDestroyFunc,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
}
|
||||
onComplete := NewCDelegate(onCompleteFn, onCompleteData, onCompleteDataDestroy)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onComplete.Dispose()
|
||||
defer onDiagnostic.Dispose()
|
||||
|
||||
onDiagnostic := makeCBuildDiagnosticCallback(onDiagnostic)
|
||||
|
||||
bundle, err := build(options, onDiagnostic)
|
||||
|
||||
var cBundle, cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cBundle = C.CString(bundle)
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_build_complete_func(onComplete.Func, cBundle, cErrorMessage, onComplete.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch
|
||||
func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr,
|
||||
onReadyFn C.FridaWatchReadyFunc, onReadyData unsafe.Pointer, onReadyDataDestroy C.FridaDestroyFunc,
|
||||
onStartingFn C.FridaStartingFunc, onStartingData unsafe.Pointer,
|
||||
onFinishedFn C.FridaFinishedFunc, onFinishedData unsafe.Pointer,
|
||||
onOutputFn C.FridaOutputFunc, onOutputData unsafe.Pointer,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
}
|
||||
onReady := NewCDelegate(onReadyFn, onReadyData, onReadyDataDestroy)
|
||||
onStarting := NewCDelegate(onStartingFn, onStartingData, nil)
|
||||
onFinished := NewCDelegate(onFinishedFn, onFinishedData, nil)
|
||||
onOutput := NewCDelegate(onOutputFn, onOutputData, nil)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onReady.Dispose()
|
||||
|
||||
onDispose := func() {
|
||||
onStarting.Dispose()
|
||||
onFinished.Dispose()
|
||||
onOutput.Dispose()
|
||||
onDiagnostic.Dispose()
|
||||
}
|
||||
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnStart: func() {
|
||||
C.invoke_starting_func(onStarting.Func, onStarting.Data)
|
||||
},
|
||||
OnEnd: func() {
|
||||
C.invoke_starting_func(onFinished.Func, onFinished.Data)
|
||||
},
|
||||
OnOutput: func(bundle string) {
|
||||
C.invoke_output_func(onOutput.Func, C.CString(bundle), onOutput.Data)
|
||||
},
|
||||
OnDiagnostic: makeCBuildDiagnosticCallback(onDiagnostic),
|
||||
}
|
||||
|
||||
session, err := NewWatchSession(options, onDispose, callbacks)
|
||||
|
||||
var cSession C.uintptr_t
|
||||
var cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cSession = C.uintptr_t(cgo.NewHandle(session))
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_watch_ready_func(onReady.Func, cSession, cErrorMessage, onReady.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch_session_dispose
|
||||
func _frida_compiler_backend_watch_session_dispose(h uintptr) {
|
||||
handle := cgo.Handle(h)
|
||||
session := handle.Value().(*WatchSession)
|
||||
session.Dispose()
|
||||
handle.Delete()
|
||||
}
|
||||
|
||||
func makeCBuildDiagnosticCallback(onDiagnostic *CDelegate[C.FridaDiagnosticFunc]) BuildDiagnosticCallback {
|
||||
return func(d Diagnostic) {
|
||||
var cPath *C.char
|
||||
if d.path != "" {
|
||||
cPath = C.CString(d.path)
|
||||
}
|
||||
|
||||
C.invoke_diagnostic_func(onDiagnostic.Func, C.CString(d.category), C.int(d.code), cPath, C.int(d.line),
|
||||
C.int(d.character), C.CString(d.text), onDiagnostic.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func build(options BuildOptions, onDiagnostic BuildDiagnosticCallback) (bundle string, err error) {
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnOutput: func(b string) {
|
||||
bundle = b
|
||||
},
|
||||
OnDiagnostic: onDiagnostic,
|
||||
}
|
||||
|
||||
ctx, err := makeContext(options, callbacks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer ctx.Dispose()
|
||||
|
||||
result := ctx.Rebuild()
|
||||
|
||||
if len(result.Errors) != 0 {
|
||||
err = fmt.Errorf("Compilation failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type WatchSession struct {
|
||||
mu sync.Mutex
|
||||
options BuildOptions
|
||||
callbacks BuildEventCallbacks
|
||||
ctx esbuild.BuildContext
|
||||
onDispose SessionDisposeHandler
|
||||
}
|
||||
|
||||
type SessionDisposeHandler func()
|
||||
|
||||
func NewWatchSession(opts BuildOptions, onDispose SessionDisposeHandler, callbacks BuildEventCallbacks) (session *WatchSession, err error) {
|
||||
cbs := BuildEventCallbacks{
|
||||
OnStart: callbacks.OnStart,
|
||||
OnEnd: callbacks.OnEnd,
|
||||
OnOutput: callbacks.OnOutput,
|
||||
OnDiagnostic: callbacks.OnDiagnostic,
|
||||
OnConfigChange: func() {
|
||||
if callbacks.OnConfigChange != nil {
|
||||
callbacks.OnConfigChange()
|
||||
}
|
||||
session.onConfigChange()
|
||||
},
|
||||
}
|
||||
|
||||
ctx, err := makeContext(opts, cbs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Watch(esbuild.WatchOptions{})
|
||||
|
||||
session = &WatchSession{
|
||||
options: opts,
|
||||
callbacks: cbs,
|
||||
ctx: ctx,
|
||||
onDispose: onDispose,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *WatchSession) Dispose() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.ctx != nil {
|
||||
s.ctx.Dispose()
|
||||
s.ctx = nil
|
||||
}
|
||||
|
||||
s.onDispose()
|
||||
}
|
||||
|
||||
func (s *WatchSession) onConfigChange() {
|
||||
go func() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.ctx != nil {
|
||||
s.ctx.Dispose()
|
||||
s.ctx = nil
|
||||
}
|
||||
|
||||
ctx, err := makeContext(s.options, s.callbacks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.ctx = ctx
|
||||
|
||||
ctx.Watch(esbuild.WatchOptions{})
|
||||
}()
|
||||
}
|
||||
|
||||
func makeContext(options BuildOptions, callbacks BuildEventCallbacks) (ctx esbuild.BuildContext, err error) {
|
||||
var e error
|
||||
|
||||
var projectRoot string
|
||||
if projectRoot, e = filepath.EvalSymlinks(options.ProjectRoot); e != nil {
|
||||
err = fmt.Errorf("Failed to resolve project root: %w", e)
|
||||
return
|
||||
}
|
||||
|
||||
var entrypoint string
|
||||
if filepath.IsAbs(options.Entrypoint) {
|
||||
entrypoint = options.Entrypoint
|
||||
} else {
|
||||
entrypoint = filepath.Join(projectRoot, options.Entrypoint)
|
||||
}
|
||||
if entrypoint, e = filepath.EvalSymlinks(entrypoint); e != nil {
|
||||
err = fmt.Errorf("Failed to resolve entrypoint: %w", e)
|
||||
return
|
||||
}
|
||||
rel, e := filepath.Rel(projectRoot, entrypoint)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Could not compute entrypoint path relative to project root: %w", e)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
err = fmt.Errorf("Entrypoint must be inside the project root")
|
||||
return
|
||||
}
|
||||
|
||||
isTS := strings.HasSuffix(entrypoint, ".ts")
|
||||
|
||||
var tsconfigCache *TSConfigCache
|
||||
var tsconfigText string
|
||||
var tsCompiler *TSCompiler
|
||||
|
||||
if isTS {
|
||||
tsconfigCache = NewTSConfigCache(projectRoot, options.SourceMap, callbacks.OnConfigChange)
|
||||
|
||||
tsCompiler = NewTSCompiler(projectRoot, entrypoint, tsconfigCache.GetCompilerOptions)
|
||||
|
||||
_, tsconfigText, err = tsconfigCache.GetCompilerOptions(tsCompiler)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to load tsconfig options: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sourcemapOption := esbuild.SourceMapNone
|
||||
if options.SourceMap {
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
sourcemapOption = esbuild.SourceMapExternal
|
||||
} else {
|
||||
sourcemapOption = esbuild.SourceMapInline
|
||||
}
|
||||
}
|
||||
|
||||
minifyWhitespace := false
|
||||
minifyIdentifiers := false
|
||||
minifySyntax := false
|
||||
if options.Compress {
|
||||
minifyWhitespace = true
|
||||
minifyIdentifiers = true
|
||||
minifySyntax = true
|
||||
}
|
||||
|
||||
var format esbuild.Format
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
format = esbuild.FormatESModule
|
||||
} else {
|
||||
format = esbuild.FormatIIFE
|
||||
}
|
||||
|
||||
plugins := []esbuild.Plugin{
|
||||
makeBuildObserverPlugin(projectRoot, entrypoint, options, callbacks),
|
||||
}
|
||||
|
||||
if isTS && !options.DisableTypeCheck {
|
||||
plugins = append(plugins, makeTypeScriptPlugin(tsCompiler))
|
||||
}
|
||||
|
||||
plugins = append(plugins, makeFridaShimsPlugin())
|
||||
|
||||
buildOpts := esbuild.BuildOptions{
|
||||
Sourcemap: sourcemapOption,
|
||||
SourcesContent: esbuild.SourcesContentExclude,
|
||||
Target: esbuild.ES2022,
|
||||
MinifyWhitespace: minifyWhitespace,
|
||||
MinifyIdentifiers: minifyIdentifiers,
|
||||
MinifySyntax: minifySyntax,
|
||||
LegalComments: esbuild.LegalCommentsNone,
|
||||
Bundle: true,
|
||||
Outdir: projectRoot,
|
||||
AbsWorkingDir: projectRoot,
|
||||
Platform: esbuild.PlatformNode,
|
||||
Format: format,
|
||||
Inject: []string{"frida-builtins:///node-globals.js"},
|
||||
EntryPoints: []string{entrypoint},
|
||||
Write: false,
|
||||
Plugins: plugins,
|
||||
}
|
||||
|
||||
if isTS {
|
||||
buildOpts.TsconfigRaw = tsconfigText
|
||||
}
|
||||
|
||||
if buildCtx, ctxErr := esbuild.Context(buildOpts); ctxErr == nil {
|
||||
ctx = buildCtx
|
||||
} else {
|
||||
for _, e := range ctxErr.Errors {
|
||||
emitDiagnostic("error", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
err = fmt.Errorf("Failed to create ESBuild context")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func emitDiagnostic(category string, message esbuild.Message, onDiagnostic BuildDiagnosticCallback) {
|
||||
d := Diagnostic{
|
||||
category: category,
|
||||
code: -1,
|
||||
text: message.Text,
|
||||
}
|
||||
|
||||
l := message.Location
|
||||
if l != nil {
|
||||
d.path = l.File
|
||||
d.line = l.Line
|
||||
d.character = l.Column
|
||||
}
|
||||
|
||||
if message.PluginName == "frida-custom-ts" && len(message.Notes) == 2 {
|
||||
d.category = message.Notes[0].Text
|
||||
fmt.Sscan(message.Notes[1].Text, &d.code)
|
||||
}
|
||||
|
||||
onDiagnostic(d)
|
||||
}
|
||||
|
||||
func changeToJS(p string) string {
|
||||
ext := path.Ext(p)
|
||||
base := p[:len(p)-len(ext)]
|
||||
return base + ".js"
|
||||
}
|
||||
|
||||
func makeBuildObserverPlugin(projectRoot, entrypoint string, options BuildOptions, callbacks BuildEventCallbacks) esbuild.Plugin {
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-build-observer",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
if callbacks.OnStart != nil {
|
||||
build.OnStart(func() (esbuild.OnStartResult, error) {
|
||||
callbacks.OnStart()
|
||||
return esbuild.OnStartResult{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
build.OnEnd(func(result *esbuild.BuildResult) (esbuild.OnEndResult, error) {
|
||||
if len(result.Errors) == 0 {
|
||||
var output string
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
output = makeESMBundle(result.OutputFiles, projectRoot, entrypoint)
|
||||
} else {
|
||||
output = string(result.OutputFiles[0].Contents)
|
||||
}
|
||||
|
||||
switch options.OutputFormat {
|
||||
case OutputFormatHexBytes:
|
||||
output = encodeStringToHexBytes(output)
|
||||
case OutputFormatCString:
|
||||
output = encodeStringToCString(output)
|
||||
}
|
||||
|
||||
callbacks.OnOutput(output)
|
||||
} else {
|
||||
for _, e := range result.Errors {
|
||||
emitDiagnostic("error", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
for _, e := range result.Warnings {
|
||||
emitDiagnostic("warning", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
if callbacks.OnEnd != nil {
|
||||
callbacks.OnEnd()
|
||||
}
|
||||
|
||||
return esbuild.OnEndResult{}, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeESMBundle(files []esbuild.OutputFile, projectRoot, entrypoint string) string {
|
||||
entrypointJS, entrypointMap := outputForEntrypoint(projectRoot, entrypoint)
|
||||
relEntryp, err := filepath.Rel(projectRoot, entrypoint)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Cannot compute entrypoint relative path: %v", err))
|
||||
}
|
||||
entrySubDir := filepath.Dir(relEntryp)
|
||||
|
||||
entryIndexJS := -1
|
||||
entryIndexMap := -1
|
||||
for i, of := range files {
|
||||
if of.Path == entrypointJS {
|
||||
entryIndexJS = i
|
||||
}
|
||||
if of.Path == entrypointMap {
|
||||
entryIndexMap = i
|
||||
}
|
||||
}
|
||||
if entryIndexJS < 0 {
|
||||
panic(fmt.Sprintf("Entrypoint JS %q not found in files", entrypointJS))
|
||||
}
|
||||
|
||||
orderedFiles := make([]esbuild.OutputFile, 0, len(files))
|
||||
orderedFiles = append(orderedFiles, files[entryIndexJS])
|
||||
if entryIndexMap >= 0 {
|
||||
orderedFiles = append(orderedFiles, files[entryIndexMap])
|
||||
}
|
||||
|
||||
for i, of := range files {
|
||||
if i == entryIndexJS || i == entryIndexMap {
|
||||
continue
|
||||
}
|
||||
orderedFiles = append(orderedFiles, of)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("📦\n")
|
||||
for _, of := range orderedFiles {
|
||||
size := len(of.Contents)
|
||||
relPath, _ := filepath.Rel(projectRoot, of.Path)
|
||||
withSub := filepath.Join(entrySubDir, relPath)
|
||||
sb.WriteString(fmt.Sprintf("%d /%s\n", size, filepath.ToSlash(withSub)))
|
||||
}
|
||||
|
||||
sb.WriteString("✄\n")
|
||||
for i, of := range orderedFiles {
|
||||
sb.Write(of.Contents)
|
||||
if i < len(orderedFiles)-1 {
|
||||
sb.WriteString("\n✄\n")
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func outputForEntrypoint(projectRoot, entrypoint string) (jsOut, mapOut string) {
|
||||
jsOut = filepath.Join(projectRoot, changeToJS(filepath.Base(entrypoint)))
|
||||
mapOut = jsOut + ".map"
|
||||
return
|
||||
}
|
||||
|
||||
func encodeStringToHexBytes(s string) string {
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
totalLen := n*4 + (n-1)*2 + 1
|
||||
buf := make([]byte, 0, totalLen)
|
||||
|
||||
hexDigits := "0123456789abcdef"
|
||||
for i := 0; i < n; i++ {
|
||||
b := s[i]
|
||||
buf = append(buf, '0', 'x')
|
||||
buf = append(buf,
|
||||
hexDigits[b>>4],
|
||||
hexDigits[b&0xF],
|
||||
)
|
||||
|
||||
if i < n-1 {
|
||||
if (i+1)%12 == 0 {
|
||||
buf = append(buf, ',', '\n')
|
||||
} else {
|
||||
buf = append(buf, ',', ' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, '\n')
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func encodeStringToCString(s string) string {
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return `""`
|
||||
}
|
||||
|
||||
hexDigits := "0123456789abcdef"
|
||||
var bldr strings.Builder
|
||||
|
||||
openLiteral := func() {
|
||||
bldr.WriteByte('"')
|
||||
}
|
||||
closeLiteral := func() {
|
||||
bldr.WriteByte('"')
|
||||
}
|
||||
|
||||
inLiteral := false
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
c := s[i]
|
||||
|
||||
if !inLiteral {
|
||||
openLiteral()
|
||||
inLiteral = true
|
||||
}
|
||||
|
||||
if c == '\n' {
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('n')
|
||||
closeLiteral()
|
||||
inLiteral = false
|
||||
|
||||
if i < n-1 {
|
||||
bldr.WriteByte('\n')
|
||||
openLiteral()
|
||||
inLiteral = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '"':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('"')
|
||||
case '\\':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('\\')
|
||||
|
||||
case '\a':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('a')
|
||||
case '\b':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('b')
|
||||
case '\t':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('t')
|
||||
case '\v':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('v')
|
||||
case '\f':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('f')
|
||||
case '\r':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('r')
|
||||
|
||||
default:
|
||||
isPrintableExceptQuoteAndBackslash := c >= 0x20 && c <= 0x7E
|
||||
if isPrintableExceptQuoteAndBackslash {
|
||||
bldr.WriteByte(c)
|
||||
} else {
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('x')
|
||||
bldr.WriteByte(hexDigits[c>>4])
|
||||
bldr.WriteByte(hexDigits[c&0xF])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inLiteral {
|
||||
closeLiteral()
|
||||
}
|
||||
|
||||
bldr.WriteByte('\n')
|
||||
|
||||
return bldr.String()
|
||||
}
|
||||
|
||||
func makeTypeScriptPlugin(compiler *TSCompiler) esbuild.Plugin {
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-custom-ts",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
build.OnStart(func() (esbuild.OnStartResult, error) {
|
||||
compiler.EnsureProgramUpToDate()
|
||||
return esbuild.OnStartResult{}, nil
|
||||
})
|
||||
|
||||
build.OnEnd(func(result *esbuild.BuildResult) (esbuild.OnEndResult, error) {
|
||||
if len(result.Errors) == 0 {
|
||||
compiler.CommitPendingInputs()
|
||||
} else {
|
||||
compiler.DropPendingInputs()
|
||||
}
|
||||
return esbuild.OnEndResult{}, nil
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: "\\.ts$"}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
compiledJS, tsDiagnostics, err := compiler.Compile(args.Path)
|
||||
|
||||
result := esbuild.OnLoadResult{
|
||||
WatchFiles: compiler.WatchFiles(),
|
||||
WatchDirs: compiler.WatchDirs(),
|
||||
}
|
||||
|
||||
var esbuildMessages []esbuild.Message
|
||||
for _, d := range tsDiagnostics {
|
||||
f := d.File()
|
||||
pos := d.Pos()
|
||||
line, column := tsscanner.GetLineAndCharacterOfPosition(f, pos)
|
||||
|
||||
esbuildMessages = append(esbuildMessages, esbuild.Message{
|
||||
Text: d.Message(),
|
||||
Location: &esbuild.Location{
|
||||
File: f.FileName(),
|
||||
Line: line,
|
||||
Column: column,
|
||||
Length: d.Len(),
|
||||
LineText: f.Text()[pos:d.End()],
|
||||
},
|
||||
Notes: []esbuild.Note{
|
||||
{Text: d.Category().Name()},
|
||||
{Text: fmt.Sprintf("%d", d.Code())},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mainErr := esbuild.Message{Text: err.Error()}
|
||||
result.Errors = append([]esbuild.Message{mainErr}, esbuildMessages...)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if len(esbuildMessages) > 0 {
|
||||
result.Errors = esbuildMessages
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.Contents = &compiledJS
|
||||
result.Loader = esbuild.LoaderJS
|
||||
return result, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CDelegate[F any] struct {
|
||||
Func F
|
||||
null F
|
||||
Data unsafe.Pointer
|
||||
dataDestroy C.FridaDestroyFunc
|
||||
}
|
||||
|
||||
func NewCDelegate[F any](function F, data unsafe.Pointer, dataDestroy C.FridaDestroyFunc) *CDelegate[F] {
|
||||
return &CDelegate[F]{Func: function, Data: data, dataDestroy: dataDestroy}
|
||||
}
|
||||
|
||||
func (d *CDelegate[F]) Dispose() {
|
||||
if d.dataDestroy != nil {
|
||||
C.invoke_destroy_func(d.dataDestroy, d.Data)
|
||||
}
|
||||
d.Func = d.null
|
||||
d.Data = nil
|
||||
d.dataDestroy = nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
global:
|
||||
_frida_compiler_backend_*;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -0,0 +1,375 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def main(argv):
|
||||
go_config = json.loads(base64.b64decode(argv[1]).decode("utf-8"))
|
||||
output_dir, priv_dir, go, npm, *inputs = [Path(d).resolve() for d in argv[2:]]
|
||||
|
||||
try:
|
||||
build_backend(go_config, inputs, output_dir, priv_dir, go, npm)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e, file=sys.stderr)
|
||||
print(
|
||||
"Output:\n\t| " + "\n\t| ".join(e.output.strip().split("\n")),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def build_backend(
|
||||
config: dict,
|
||||
inputs: List[Path],
|
||||
output_dir: Path,
|
||||
priv_dir: Path,
|
||||
go: Path,
|
||||
npm: Path,
|
||||
):
|
||||
base_dir = min(inputs, key=lambda p: len(p.parts)).parent
|
||||
for f in inputs:
|
||||
dest = priv_dir / f.relative_to(base_dir)
|
||||
dest.parent.mkdir(exist_ok=True)
|
||||
shutil.copy(f, dest)
|
||||
|
||||
go_sources = [str(f.relative_to(base_dir)) for f in inputs if f.suffix == ".go"]
|
||||
|
||||
def run(*args):
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=priv_dir,
|
||||
env=config["env"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
|
||||
run(npm, "install")
|
||||
# run(npm, "link", "/home/oleavr/src/frida-fs")
|
||||
|
||||
mode = config["mode"]
|
||||
|
||||
backend_stem = "frida-compiler-backend"
|
||||
if mode == "c-shared":
|
||||
backend_stem += "-raw"
|
||||
|
||||
backend_a = priv_dir / f"{backend_stem}.a"
|
||||
backend_shlib = priv_dir / (f"{backend_stem}" + config["shlib_suffix"])
|
||||
backend_h = priv_dir / f"{backend_stem}.h"
|
||||
|
||||
extra_go_args = config["extra_go_args"].copy()
|
||||
|
||||
if mode == "c-shared":
|
||||
version_script = priv_dir / "backend.version"
|
||||
extra_go_args.append(f"-ldflags=-linkmode=external -extldflags=-Wl,--version-script={version_script}")
|
||||
|
||||
run(
|
||||
go,
|
||||
"build",
|
||||
f"-buildmode={mode}",
|
||||
"-o",
|
||||
backend_a.name if mode == "c-archive" else backend_shlib.name,
|
||||
"-buildvcs=false",
|
||||
*extra_go_args,
|
||||
*go_sources,
|
||||
)
|
||||
|
||||
if mode == "c-archive":
|
||||
symbols_to_scramble = detect_conflictful_symbols_in_archive(backend_a, config["nm"])
|
||||
scramble_symbols_in_archive(backend_a, symbols_to_scramble, config["ranlib"])
|
||||
|
||||
if (mingw := config.get("mingw")) is not None and (abi := config["abi"]) in {
|
||||
"x86",
|
||||
"x86_64",
|
||||
}:
|
||||
prefix = Path(mingw["prefix"])
|
||||
ar = config["ar"]
|
||||
|
||||
libmingwex_a = prefix / "lib" / "libmingwex.a"
|
||||
if not libmingwex_a.exists():
|
||||
libmingwex_a = prefix / mingw["triplet"] / "lib" / "libmingwex.a"
|
||||
|
||||
libgcc_objects = [
|
||||
"_chkstk_ms.o",
|
||||
]
|
||||
run(*ar, "x", mingw["libcc"], *libgcc_objects)
|
||||
|
||||
gwex_objflavor = "64" if abi == "x86_64" else "32"
|
||||
gwex_objects = [
|
||||
f"lib{gwex_objflavor}_libmingwex_a-{name}.o"
|
||||
for name in [
|
||||
"dmisc",
|
||||
"gdtoa",
|
||||
"gmisc",
|
||||
"mingw_fprintf",
|
||||
"mingw_pformat",
|
||||
"misc",
|
||||
]
|
||||
]
|
||||
run(*ar, "x", libmingwex_a, *gwex_objects)
|
||||
|
||||
extra_objects = []
|
||||
if abi == "x86_64":
|
||||
run(*ar, "x", backend_a.name, "go.o")
|
||||
sort_pdata_in_object(Path(priv_dir) / "go.o")
|
||||
extra_objects.append("go.o")
|
||||
|
||||
run(*ar, "rs", backend_a.name, *libgcc_objects, *gwex_objects, *extra_objects)
|
||||
|
||||
shutil.copy(priv_dir / backend_a.name, output_dir / backend_a.name)
|
||||
else:
|
||||
shutil.copy(priv_dir / backend_shlib.name, output_dir / backend_shlib.name)
|
||||
|
||||
shutil.copy(priv_dir / backend_h.name, output_dir / backend_h.name)
|
||||
|
||||
|
||||
def detect_conflictful_symbols_in_archive(
|
||||
archive: Path, nm_cmd_array: List[str]
|
||||
) -> List[str]:
|
||||
result = []
|
||||
for line in subprocess.run(
|
||||
[*nm_cmd_array, str(archive)], capture_output=True, encoding="utf-8", check=True
|
||||
).stdout.splitlines():
|
||||
tokens = line.lstrip().split(" ")
|
||||
if len(tokens) >= 3 and tokens[1] in {"S", "T"}:
|
||||
symbol = tokens[2]
|
||||
if all(sub not in symbol for sub in ("/", ".", ":", "frida")):
|
||||
result.append(symbol)
|
||||
return result
|
||||
|
||||
|
||||
def scramble_symbols_in_archive(
|
||||
archive: Path, symbols: List[str], ranlib_cmd_array: List[str]
|
||||
):
|
||||
data = archive.read_bytes()
|
||||
|
||||
for old_sym in symbols:
|
||||
new_sym = roll_first_alpha(old_sym)
|
||||
data = data.replace(old_sym.encode("ascii"), new_sym.encode("ascii"))
|
||||
|
||||
archive.write_bytes(data)
|
||||
|
||||
subprocess.run([*ranlib_cmd_array, str(archive)], check=True)
|
||||
|
||||
|
||||
def roll_first_alpha(s: str) -> str:
|
||||
chars = list(s)
|
||||
for i, c in enumerate(chars):
|
||||
if c.isalpha():
|
||||
if c == "z":
|
||||
chars[i] = "a"
|
||||
elif c == "Z":
|
||||
chars[i] = "A"
|
||||
else:
|
||||
chars[i] = chr(ord(c) + 1)
|
||||
break
|
||||
return "".join(chars)
|
||||
|
||||
|
||||
# Work around the Go toolchain's almost-MSVC-compatible object files
|
||||
# until our patch is merged upstream:
|
||||
# https://go-review.googlesource.com/c/go/+/678795
|
||||
def sort_pdata_in_object(object_file: Path):
|
||||
data = bytearray(object_file.read_bytes())
|
||||
|
||||
# --- Parse COFF File Header (IMAGE_FILE_HEADER) ---
|
||||
# Format: WORD Machine; WORD NumberOfSections;
|
||||
# DWORD TimeDateStamp; DWORD PointerToSymbolTable;
|
||||
# DWORD NumberOfSymbols; WORD SizeOfOptionalHeader;
|
||||
# WORD Characteristics;
|
||||
IMAGE_FILE_HEADER_FMT = "<HHLLLHH"
|
||||
IMAGE_FILE_HEADER_SIZE = struct.calcsize(IMAGE_FILE_HEADER_FMT)
|
||||
|
||||
if len(data) < IMAGE_FILE_HEADER_SIZE:
|
||||
print("Error: File too small to be a valid COFF object", file=sys.stderr)
|
||||
return
|
||||
|
||||
(
|
||||
machine,
|
||||
number_of_sections,
|
||||
time_date_stamp,
|
||||
pointer_to_symbol_table,
|
||||
number_of_symbols,
|
||||
size_of_optional_header,
|
||||
characteristics,
|
||||
) = struct.unpack(IMAGE_FILE_HEADER_FMT, data[0:IMAGE_FILE_HEADER_SIZE])
|
||||
|
||||
# --- Parse Section Headers to find .pdata ---
|
||||
# Each IMAGE_SECTION_HEADER is 40 bytes: Name[8], Misc(VirtualSize), VirtualAddress,
|
||||
# SizeOfRawData, PointerToRawData, PointerToRelocations, PointerToLinenumbers,
|
||||
# NumberOfRelocations, NumberOfLinenumbers, Characteristics
|
||||
SECTION_HEADER_FMT = "<8sLLLLLLHHI"
|
||||
SECTION_HEADER_SIZE = struct.calcsize(SECTION_HEADER_FMT)
|
||||
section_table_offset = IMAGE_FILE_HEADER_SIZE + size_of_optional_header
|
||||
|
||||
pdata_section_index = None
|
||||
pdata_pointer_to_raw = None
|
||||
pdata_size_of_raw = None
|
||||
pdata_pointer_to_relocs = None
|
||||
pdata_number_of_relocs = None
|
||||
|
||||
# Keep all section headers for rewriting relocation entries later if needed
|
||||
section_headers = []
|
||||
|
||||
for i in range(number_of_sections):
|
||||
offset = section_table_offset + i * SECTION_HEADER_SIZE
|
||||
section_data = data[offset : offset + SECTION_HEADER_SIZE]
|
||||
(
|
||||
name,
|
||||
virtual_size,
|
||||
virtual_address,
|
||||
size_of_raw_data,
|
||||
pointer_to_raw_data,
|
||||
pointer_to_relocations,
|
||||
pointer_to_linenumbers,
|
||||
number_of_relocations,
|
||||
number_of_linenumbers,
|
||||
sec_characteristics,
|
||||
) = struct.unpack(SECTION_HEADER_FMT, section_data)
|
||||
|
||||
section_name = name.rstrip(b"\x00").decode("utf-8")
|
||||
section_headers.append(
|
||||
{
|
||||
"name": section_name,
|
||||
"offset": offset,
|
||||
"virtual_size": virtual_size,
|
||||
"virtual_address": virtual_address,
|
||||
"size_of_raw_data": size_of_raw_data,
|
||||
"pointer_to_raw_data": pointer_to_raw_data,
|
||||
"pointer_to_relocations": pointer_to_relocations,
|
||||
"number_of_relocations": number_of_relocations,
|
||||
}
|
||||
)
|
||||
|
||||
if section_name == ".pdata":
|
||||
pdata_section_index = i
|
||||
pdata_pointer_to_raw = pointer_to_raw_data
|
||||
pdata_size_of_raw = size_of_raw_data
|
||||
pdata_pointer_to_relocs = pointer_to_relocations
|
||||
pdata_number_of_relocs = number_of_relocations
|
||||
|
||||
if pdata_section_index is None:
|
||||
print("Error: No .pdata section found in the object file", file=sys.stderr)
|
||||
return
|
||||
|
||||
# --- Parse Symbol Table ---
|
||||
# Each IMAGE_SYMBOL is 18 bytes: Name[8], Value (4 bytes), SectionNumber (2 bytes),
|
||||
# Type (2 bytes), StorageClass (1 byte), NumberOfAuxSymbols (1 byte)
|
||||
SYMBOL_TABLE_ENTRY_SIZE = 18
|
||||
symbols = []
|
||||
for sym_index in range(number_of_symbols):
|
||||
sym_offset = pointer_to_symbol_table + sym_index * SYMBOL_TABLE_ENTRY_SIZE
|
||||
entry = data[sym_offset : sym_offset + SYMBOL_TABLE_ENTRY_SIZE]
|
||||
if len(entry) < SYMBOL_TABLE_ENTRY_SIZE:
|
||||
print("Error: Truncated symbol table entry", file=sys.stderr)
|
||||
return
|
||||
# Unpack: Name (8s), Value (L), SectionNumber (h), Type (H), StorageClass (B), NumberOfAuxSymbols (B)
|
||||
name_bytes, value, section_number, sym_type, storage_class, num_aux = (
|
||||
struct.unpack("<8sLhHBB", entry)
|
||||
)
|
||||
# Decode name: if name_bytes[0:4] are zeros, use string from offset in string table
|
||||
if name_bytes[:4] == b"\x00\x00\x00\x00":
|
||||
str_table_offset = struct.unpack("<L", name_bytes[4:])[0]
|
||||
# String table starts immediately after symbol table: size (4 bytes) + NUL-terminated strings
|
||||
str_tab_start = (
|
||||
pointer_to_symbol_table + number_of_symbols * SYMBOL_TABLE_ENTRY_SIZE
|
||||
)
|
||||
# Fetch the name from string table
|
||||
str_offset = str_tab_start + str_table_offset
|
||||
name_end = data.find(b"\x00", str_offset)
|
||||
symbol_name = data[str_offset:name_end].decode("utf-8")
|
||||
else:
|
||||
symbol_name = name_bytes.partition(b"\x00")[0].decode("utf-8")
|
||||
|
||||
symbols.append(
|
||||
{"name": symbol_name, "value": value, "section_number": section_number}
|
||||
)
|
||||
|
||||
# --- Read original .pdata section raw data ---
|
||||
pdata_data = data[pdata_pointer_to_raw : pdata_pointer_to_raw + pdata_size_of_raw]
|
||||
if len(pdata_data) % 12 != 0:
|
||||
print("Error: .pdata size is not a multiple of 12 bytes", file=sys.stderr)
|
||||
return
|
||||
num_entries = len(pdata_data) // 12
|
||||
|
||||
# --- Read relocation entries for .pdata ---
|
||||
# Each IMAGE_RELOCATION is 10 bytes: VirtualAddress (4), SymbolTableIndex (4), Type (2)
|
||||
RELOC_ENTRY_SIZE = 10
|
||||
reloc_entries = []
|
||||
reloc_table_start = pdata_pointer_to_relocs
|
||||
for rindex in range(pdata_number_of_relocs):
|
||||
reloff = reloc_table_start + rindex * RELOC_ENTRY_SIZE
|
||||
entry = data[reloff : reloff + RELOC_ENTRY_SIZE]
|
||||
if len(entry) < RELOC_ENTRY_SIZE:
|
||||
print("Error: Truncated relocation entry", file=sys.stderr)
|
||||
return
|
||||
va, sym_index, rtype = struct.unpack("<LLH", entry)
|
||||
reloc_entries.append(
|
||||
{"virtual_address": va, "symbol_index": sym_index, "type": rtype}
|
||||
)
|
||||
|
||||
# There should be 3 relocations per .pdata entry
|
||||
if len(reloc_entries) != num_entries * 3:
|
||||
print(
|
||||
"Warning: Expected {} relocations but found {}".format(
|
||||
num_entries * 3, len(reloc_entries)
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# --- Associate each entry with its relocations and sorting key ---
|
||||
entries = []
|
||||
for i in range(num_entries):
|
||||
entry_bytes = pdata_data[i * 12 : (i + 1) * 12]
|
||||
entry_relocs = reloc_entries[i * 3 : i * 3 + 3]
|
||||
|
||||
# Sorting key: use symbol.Value of first relocation"s symbol index
|
||||
sym_idx = entry_relocs[0]["symbol_index"]
|
||||
if sym_idx < 0 or sym_idx >= number_of_symbols:
|
||||
key = 0
|
||||
else:
|
||||
key = symbols[sym_idx]["value"]
|
||||
|
||||
entries.append({"entry_bytes": entry_bytes, "relocs": entry_relocs, "key": key})
|
||||
|
||||
# --- Sort entries by computed key ---
|
||||
entries.sort(key=lambda x: x["key"])
|
||||
|
||||
# --- Build new .pdata data buffer ---
|
||||
sorted_pdata = bytearray()
|
||||
for e in entries:
|
||||
sorted_pdata.extend(e["entry_bytes"])
|
||||
|
||||
# --- Build new relocation table buffer ---
|
||||
new_reloc_buf = bytearray()
|
||||
for new_i, e in enumerate(entries):
|
||||
for reloc in e["relocs"]:
|
||||
field_offset = reloc["virtual_address"] % 12
|
||||
new_va = new_i * 12 + field_offset
|
||||
packed = struct.pack("<LLH", new_va, reloc["symbol_index"], reloc["type"])
|
||||
new_reloc_buf.extend(packed)
|
||||
|
||||
# --- Write back sorted .pdata and new relocations into data buffer ---
|
||||
# Replace .pdata raw data
|
||||
data[pdata_pointer_to_raw : pdata_pointer_to_raw + pdata_size_of_raw] = sorted_pdata
|
||||
# Replace relocation entries
|
||||
data[pdata_pointer_to_relocs : pdata_pointer_to_relocs + len(new_reloc_buf)] = (
|
||||
new_reloc_buf
|
||||
)
|
||||
|
||||
# --- Emit the fixed object file ---
|
||||
object_file.write_bytes(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -1,3 +1,2 @@
|
||||
[resource-compiler]
|
||||
namespace = Frida.Data.Compiler
|
||||
compress = *.js:text:7; *.bin:generic:7
|
||||
@@ -1,3 +0,0 @@
|
||||
[resource-compiler]
|
||||
namespace = Frida.Data.Compiler
|
||||
compress = *.js:text:11; *.bin:generic:11
|
||||
+291
-341
@@ -1,63 +1,56 @@
|
||||
namespace Frida {
|
||||
public class Compiler : Object {
|
||||
public sealed class Compiler : Object {
|
||||
public signal void starting ();
|
||||
public signal void finished ();
|
||||
public signal void output (string bundle);
|
||||
public signal void diagnostics (Variant diagnostics);
|
||||
|
||||
public DeviceManager manager {
|
||||
get;
|
||||
construct;
|
||||
private size_t watch_session_handle = 0;
|
||||
private Gee.Queue<Diagnostic> pending_diagnostics = new Gee.ArrayQueue<Diagnostic> ();
|
||||
|
||||
private MainContext main_context;
|
||||
|
||||
// TODO: Remove the DeviceManager parameter.
|
||||
public Compiler (DeviceManager? manager = null) {
|
||||
Object ();
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private Promise<Agent>? load_request;
|
||||
private Gee.Map<uint, MonitorEntry> monitors = new Gee.HashMap<uint, MonitorEntry> ();
|
||||
private Gee.Set<MonitorEntry> dirty_monitors = new Gee.HashSet<MonitorEntry> ();
|
||||
private TimeoutSource? monitor_flush_timer;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
#endif
|
||||
|
||||
public Compiler (DeviceManager manager) {
|
||||
Object (manager: manager);
|
||||
static construct {
|
||||
CompilerBackend.init ();
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
public override void dispose () {
|
||||
if (load_request != null)
|
||||
close.begin ();
|
||||
|
||||
base.dispose ();
|
||||
construct {
|
||||
main_context = Frida.get_main_context ();
|
||||
}
|
||||
|
||||
private async void close () {
|
||||
try {
|
||||
var agent = yield get_agent (null);
|
||||
yield agent.close (null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
load_request = null;
|
||||
|
||||
if (monitor_flush_timer != null) {
|
||||
monitor_flush_timer.destroy ();
|
||||
monitor_flush_timer = null;
|
||||
}
|
||||
|
||||
foreach (var entry in monitors.values)
|
||||
detach_monitor_entry (entry);
|
||||
monitors.clear ();
|
||||
~Compiler () {
|
||||
cancel_watch ();
|
||||
}
|
||||
#endif
|
||||
|
||||
public async string build (string entrypoint, BuildOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
var agent = yield get_agent (cancellable);
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
BuildOptions opts = (options != null) ? options : new BuildOptions ();
|
||||
string project_root = compute_project_root (entrypoint, opts);
|
||||
|
||||
starting ();
|
||||
try {
|
||||
string bundle = yield agent.build (entrypoint, options, cancellable);
|
||||
string? bundle = null;
|
||||
string? error_message = null;
|
||||
CompilerBackend.BuildCompleteFunc on_complete = (b, e) => {
|
||||
bundle = b;
|
||||
error_message = e;
|
||||
schedule_on_frida_thread (build.callback);
|
||||
};
|
||||
|
||||
CompilerBackend.build (project_root, entrypoint, opts.output_format, opts.bundle_format,
|
||||
(size_t) (opts.type_check == NONE), (size_t) (opts.source_maps == INCLUDED),
|
||||
(size_t) (opts.compression == TERSER), (owned) on_complete, on_diagnostic);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", error_message);
|
||||
|
||||
output (bundle);
|
||||
|
||||
@@ -65,9 +58,6 @@ namespace Frida {
|
||||
} finally {
|
||||
finished ();
|
||||
}
|
||||
#else
|
||||
throw_not_supported ();
|
||||
#endif
|
||||
}
|
||||
|
||||
public string build_sync (string entrypoint, BuildOptions? options = null, Cancellable? cancellable = null)
|
||||
@@ -89,13 +79,38 @@ namespace Frida {
|
||||
|
||||
public async void watch (string entrypoint, WatchOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
var agent = yield get_agent (cancellable);
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
yield agent.watch (entrypoint, options, cancellable);
|
||||
#else
|
||||
throw_not_supported ();
|
||||
#endif
|
||||
WatchOptions opts = (options != null) ? options : new WatchOptions ();
|
||||
string project_root = compute_project_root (entrypoint, opts);
|
||||
|
||||
cancel_watch ();
|
||||
|
||||
size_t session_handle = 0;
|
||||
string? error_message = null;
|
||||
CompilerBackend.WatchReadyFunc on_ready = (h, e) => {
|
||||
session_handle = h;
|
||||
error_message = e;
|
||||
schedule_on_frida_thread (watch.callback);
|
||||
};
|
||||
|
||||
CompilerBackend.watch (project_root, entrypoint, opts.output_format, opts.bundle_format,
|
||||
(size_t) (opts.type_check == NONE), (size_t) (opts.source_maps == INCLUDED),
|
||||
(size_t) (opts.compression == TERSER), (owned) on_ready, on_starting, on_finished,
|
||||
on_output, on_diagnostic);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", error_message);
|
||||
|
||||
watch_session_handle = session_handle;
|
||||
}
|
||||
|
||||
private void cancel_watch () {
|
||||
if (watch_session_handle != 0) {
|
||||
CompilerBackend.WatchSession.dispose (watch_session_handle);
|
||||
watch_session_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void watch_sync (string entrypoint, WatchOptions? options = null, Cancellable? cancellable = null)
|
||||
@@ -115,343 +130,238 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private void on_watch_add (uint id, string path, string type, int64 polling_interval) {
|
||||
var file = File.parse_name (path);
|
||||
private void on_starting () {
|
||||
schedule_on_frida_thread (() => {
|
||||
starting ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
GLib.FileMonitor monitor;
|
||||
try {
|
||||
if (type == "file")
|
||||
monitor = file.monitor_file (FileMonitorFlags.SEND_MOVED, io_cancellable);
|
||||
else if (type == "directory")
|
||||
monitor = file.monitor_directory (FileMonitorFlags.NONE, io_cancellable);
|
||||
else
|
||||
assert_not_reached ();
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
private void on_finished () {
|
||||
schedule_on_frida_thread (() => {
|
||||
finished ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_output (owned string bundle) {
|
||||
schedule_on_frida_thread (() => {
|
||||
output (bundle);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_diagnostic (owned string category, int code, owned string? path, int line, int character,
|
||||
owned string text) {
|
||||
var diag = new Diagnostic () {
|
||||
category = category,
|
||||
code = code,
|
||||
path = path,
|
||||
line = line,
|
||||
character = character,
|
||||
text = text,
|
||||
};
|
||||
|
||||
bool schedule_emit = false;
|
||||
lock (pending_diagnostics) {
|
||||
schedule_emit = pending_diagnostics.is_empty;
|
||||
pending_diagnostics.add (diag);
|
||||
}
|
||||
|
||||
if (polling_interval != 0)
|
||||
monitor.rate_limit = (int) polling_interval;
|
||||
|
||||
var entry = new MonitorEntry (id, monitor);
|
||||
entry.file_changed.connect (on_file_changed);
|
||||
monitors[id] = entry;
|
||||
}
|
||||
|
||||
private void on_watch_remove (uint id) {
|
||||
MonitorEntry? entry;
|
||||
if (monitors.unset (id, out entry))
|
||||
detach_monitor_entry (entry);
|
||||
}
|
||||
|
||||
private void detach_monitor_entry (MonitorEntry entry) {
|
||||
dirty_monitors.remove (entry);
|
||||
|
||||
entry.file_changed.disconnect (on_file_changed);
|
||||
entry.destroy ();
|
||||
}
|
||||
|
||||
private void on_file_changed (MonitorEntry entry) {
|
||||
dirty_monitors.add (entry);
|
||||
|
||||
if (monitor_flush_timer == null) {
|
||||
var source = new TimeoutSource (50);
|
||||
source.set_callback (on_monitor_flush_tick);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
monitor_flush_timer = source;
|
||||
if (schedule_emit) {
|
||||
schedule_on_frida_thread (() => {
|
||||
emit_pending_diagnostics ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_monitor_flush_tick () {
|
||||
var settled = new Gee.ArrayList<MonitorEntry> ();
|
||||
var now = get_monotonic_time ();
|
||||
settled.add_all_iterator (dirty_monitors.filter (m => now - m.last_change >= 50000));
|
||||
|
||||
Agent agent = load_request.future.value;
|
||||
foreach (var entry in settled) {
|
||||
agent.notify_file_changed (entry.id, entry.state, io_cancellable);
|
||||
dirty_monitors.remove (entry);
|
||||
private void emit_pending_diagnostics () {
|
||||
var batch = new Gee.ArrayList<Diagnostic> ();
|
||||
lock (pending_diagnostics) {
|
||||
batch.add_all (pending_diagnostics);
|
||||
pending_diagnostics.clear ();
|
||||
}
|
||||
|
||||
if (!dirty_monitors.is_empty)
|
||||
return Source.CONTINUE;
|
||||
var builder = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
|
||||
|
||||
monitor_flush_timer = null;
|
||||
return Source.REMOVE;
|
||||
}
|
||||
|
||||
private async Agent get_agent (Cancellable? cancellable) throws Error, IOError {
|
||||
Future<Agent> future;
|
||||
if (load_request != null) {
|
||||
future = load_request.future;
|
||||
} else {
|
||||
load_request = new Promise<Agent> ();
|
||||
future = load_request.future;
|
||||
|
||||
load_agent.begin ();
|
||||
foreach (var d in batch) {
|
||||
builder.open (VariantType.VARDICT);
|
||||
builder.add ("{sv}", "category", new Variant.string (d.category));
|
||||
builder.add ("{sv}", "code", new Variant.int64 (d.code));
|
||||
if (d.path != null) {
|
||||
var b = new VariantBuilder (VariantType.VARDICT);
|
||||
b.add ("{sv}", "path", new Variant.string (d.path));
|
||||
b.add ("{sv}", "line", new Variant.int64 (d.line));
|
||||
b.add ("{sv}", "character", new Variant.int64 (d.character));
|
||||
builder.add ("{sv}", "file", b.end ());
|
||||
}
|
||||
builder.add ("{sv}", "text", new Variant.string (d.text));
|
||||
builder.close ();
|
||||
}
|
||||
|
||||
return yield future.wait_async (cancellable);
|
||||
diagnostics (builder.end ());
|
||||
}
|
||||
|
||||
private async void load_agent () {
|
||||
try {
|
||||
var device = yield manager.get_device_by_type (LOCAL, 0, io_cancellable);
|
||||
var session = yield device.get_host_session (io_cancellable);
|
||||
var agent = new Agent (this, (BaseDBusHostSession) session);
|
||||
|
||||
load_request.resolve (agent);
|
||||
} catch (GLib.Error e) {
|
||||
if ((e is Error || e is IOError.CANCELLED))
|
||||
e = new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
|
||||
load_request.reject (e);
|
||||
load_request = null;
|
||||
}
|
||||
}
|
||||
#else
|
||||
[NoReturn]
|
||||
private void throw_not_supported () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Compiler backend disabled at build-time");
|
||||
}
|
||||
#endif
|
||||
|
||||
private T create<T> () {
|
||||
return Object.new (typeof (T), parent: this);
|
||||
}
|
||||
|
||||
protected void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private class Diagnostic {
|
||||
public string category;
|
||||
public int code;
|
||||
public string? path;
|
||||
public int line;
|
||||
public int character;
|
||||
public string text;
|
||||
}
|
||||
|
||||
private abstract class CompilerTask<T> : AsyncTask<T> {
|
||||
public weak Compiler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace CompilerBackend {
|
||||
private void init () {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private class Agent : InternalAgent {
|
||||
public weak Compiler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Promise<string> source_request = new Promise<string> ();
|
||||
private Promise<Bytes?> snapshot_request = new Promise<Bytes?> ();
|
||||
|
||||
public Agent (Compiler parent, BaseDBusHostSession host_session) {
|
||||
Object (
|
||||
parent: parent,
|
||||
host_session: host_session,
|
||||
#if HAVE_V8
|
||||
script_runtime: ScriptRuntime.V8
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
_init_go_runtime ();
|
||||
build = (BuildFunc) _build;
|
||||
watch = (WatchFunc) _watch;
|
||||
WatchSession.dispose = (WatchSession.DisposeFunc) WatchSession._dispose;
|
||||
#else
|
||||
script_runtime: ScriptRuntime.DEFAULT
|
||||
#endif
|
||||
);
|
||||
}
|
||||
unowned uint8[] backend_so = Frida.Data.Compiler.get_frida_compiler_backend_so_blob ().data;
|
||||
|
||||
construct {
|
||||
load_resources ();
|
||||
}
|
||||
FileDescriptor fd;
|
||||
if (MemoryFileDescriptor.is_supported ()) {
|
||||
fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
|
||||
} else {
|
||||
string name_used;
|
||||
try {
|
||||
fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
|
||||
FileUtils.unlink (name_used);
|
||||
|
||||
private void load_resources () {
|
||||
new Thread<void> ("compiler-agent-loader", () => {
|
||||
var agent_js_blob = Data.Compiler.get_agent_js_blob ();
|
||||
Bytes agent_js_bytes = decompress (agent_js_blob.data, agent_js_blob.uncompressed_size);
|
||||
source_request.resolve ((string) agent_js_bytes.get_data ());
|
||||
var output = new UnixOutputStream (fd.handle, false);
|
||||
output.write_all (backend_so, null);
|
||||
|
||||
var snapshot_blob = Data.Compiler.get_snapshot_bin_blob ();
|
||||
Bytes snapshot = decompress (snapshot_blob.data, snapshot_blob.uncompressed_size);
|
||||
snapshot_request.resolve ((snapshot.get_size () != 0) ? snapshot : null);
|
||||
});
|
||||
}
|
||||
|
||||
public async string build (string entrypoint, BuildOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
BuildOptions opts = (options != null) ? options : new BuildOptions ();
|
||||
|
||||
Json.Node bundle = yield call ("build", new Json.Node[] {
|
||||
new Json.Node.alloc ().init_string (compute_project_root (entrypoint, opts)),
|
||||
new Json.Node.alloc ().init_string (entrypoint),
|
||||
new Json.Node.alloc ().init_string (opts.source_maps.to_nick ()),
|
||||
new Json.Node.alloc ().init_string (opts.compression.to_nick ()),
|
||||
}, null, cancellable);
|
||||
return bundle.get_string ();
|
||||
}
|
||||
|
||||
public async void watch (string entrypoint, WatchOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
WatchOptions opts = (options != null) ? options : new WatchOptions ();
|
||||
|
||||
yield call ("watch", new Json.Node[] {
|
||||
new Json.Node.alloc ().init_string (compute_project_root (entrypoint, opts)),
|
||||
new Json.Node.alloc ().init_string (entrypoint),
|
||||
new Json.Node.alloc ().init_string (opts.source_maps.to_nick ()),
|
||||
new Json.Node.alloc ().init_string (opts.compression.to_nick ()),
|
||||
}, null, cancellable);
|
||||
}
|
||||
|
||||
private static string compute_project_root (string entrypoint, CompilerOptions options) {
|
||||
string? project_root = options.project_root;
|
||||
|
||||
if (project_root != null)
|
||||
return project_root;
|
||||
|
||||
if (Path.is_absolute (entrypoint))
|
||||
return Path.get_dirname (entrypoint);
|
||||
|
||||
return Environment.get_current_dir ();
|
||||
}
|
||||
|
||||
public void notify_file_changed (uint id, FileState state, Cancellable? cancellable) {
|
||||
var notification = new Json.Builder ();
|
||||
notification
|
||||
.begin_object ()
|
||||
.set_member_name ("type")
|
||||
.add_string_value ("watch:change")
|
||||
.set_member_name ("id")
|
||||
.add_int_value (id)
|
||||
.set_member_name ("state")
|
||||
.add_string_value (Marshal.enum_to_nick<FileState> (state))
|
||||
.end_object ();
|
||||
post.begin (notification.get_root (), cancellable);
|
||||
}
|
||||
|
||||
protected override async uint get_target_pid (Cancellable? cancellable) throws Error, IOError {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
|
||||
return yield source_request.future.wait_async (cancellable);
|
||||
}
|
||||
|
||||
protected override async Bytes? load_snapshot (Cancellable? cancellable, out SnapshotTransport transport)
|
||||
throws Error, IOError {
|
||||
transport = SHARED_MEMORY;
|
||||
return yield snapshot_request.future.wait_async (cancellable);
|
||||
}
|
||||
|
||||
protected override void on_event (string type, Json.Array event) {
|
||||
if (type == "diagnostics") {
|
||||
on_build_diagnostics (event.get_array_element (1));
|
||||
} else if (type == "watch:compilation-starting") {
|
||||
parent.starting ();
|
||||
} else if (type == "watch:compilation-finished") {
|
||||
parent.finished ();
|
||||
} else if (type == "watch:bundle-updated") {
|
||||
on_watch_bundle_updated (event.get_string_element (1));
|
||||
} else if (type == "watch:add") {
|
||||
parent.on_watch_add ((uint) event.get_int_element (1), event.get_string_element (2),
|
||||
event.get_string_element (3), event.get_int_element (4));
|
||||
} else if (type == "watch:remove") {
|
||||
parent.on_watch_remove ((uint) event.get_int_element (1));
|
||||
} else {
|
||||
Posix.lseek (fd.handle, 0, Posix.SEEK_SET);
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_build_diagnostics (Json.Array diagnostics) {
|
||||
uint n = diagnostics.get_length ();
|
||||
|
||||
var builder = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
|
||||
for (uint i = 0; i != n; i++) {
|
||||
var diag = diagnostics.get_array_element (i);
|
||||
builder.open (VariantType.VARDICT);
|
||||
builder.add ("{sv}", "category", new Variant.string (diag.get_string_element (0)));
|
||||
builder.add ("{sv}", "code", new Variant.int64 (diag.get_int_element (1)));
|
||||
if (!diag.get_null_element (2)) {
|
||||
var file = diag.get_array_element (2);
|
||||
var b = new VariantBuilder (VariantType.VARDICT);
|
||||
b.add ("{sv}", "path", new Variant.string (file.get_string_element (0)));
|
||||
b.add ("{sv}", "line", new Variant.int64 (file.get_int_element (1)));
|
||||
b.add ("{sv}", "character", new Variant.int64 (file.get_int_element (2)));
|
||||
builder.add ("{sv}", "file", b.end ());
|
||||
}
|
||||
builder.add ("{sv}", "text", new Variant.string (diag.get_string_element (3)));
|
||||
builder.close ();
|
||||
}
|
||||
|
||||
parent.diagnostics (builder.end ());
|
||||
}
|
||||
|
||||
private void on_watch_bundle_updated (string bundle) {
|
||||
parent.output (bundle);
|
||||
}
|
||||
|
||||
private static Bytes decompress (uint8[] data, uint uncompressed_size) {
|
||||
var decoder = new Brotli.Decoder ();
|
||||
decoder.set_parameter (LARGE_WINDOW, 1);
|
||||
|
||||
uint8[] uncompressed_data = new uint8[uncompressed_size + 1];
|
||||
size_t available_in = data.length;
|
||||
size_t available_out = uncompressed_size;
|
||||
uint8 * next_in = data;
|
||||
uint8 * next_out = uncompressed_data;
|
||||
decoder.decompress_stream (&available_in, &next_in, &available_out, &next_out);
|
||||
uncompressed_data.length = (int) (next_out - (uint8 *) uncompressed_data);
|
||||
|
||||
return new Bytes.take ((owned) uncompressed_data);
|
||||
Module backend;
|
||||
try {
|
||||
backend = new Module ("/proc/self/fd/%d".printf (fd.handle), LOCAL);
|
||||
} catch (ModuleError e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
build = resolve_symbol (backend, "_frida_compiler_backend_build");
|
||||
watch = resolve_symbol (backend, "_frida_compiler_backend_watch");
|
||||
WatchSession.dispose = resolve_symbol (backend, "_frida_compiler_backend_watch_session_dispose");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
private class MonitorEntry : Object {
|
||||
public signal void file_changed ();
|
||||
|
||||
public uint id;
|
||||
public GLib.FileMonitor monitor;
|
||||
|
||||
public FileState state = PRISTINE;
|
||||
public int64 last_change = -1;
|
||||
|
||||
public MonitorEntry (uint id, GLib.FileMonitor monitor) {
|
||||
this.id = id;
|
||||
this.monitor = monitor;
|
||||
|
||||
monitor.changed.connect (on_file_changed);
|
||||
}
|
||||
|
||||
public void destroy () {
|
||||
monitor.changed.disconnect (on_file_changed);
|
||||
monitor.cancel ();
|
||||
}
|
||||
|
||||
private void on_file_changed (File file, File? other_file, FileMonitorEvent event) {
|
||||
switch (event) {
|
||||
case CREATED:
|
||||
state = PRISTINE;
|
||||
break;
|
||||
case CHANGED:
|
||||
if (state != PRISTINE)
|
||||
state = MODIFIED;
|
||||
break;
|
||||
case DELETED:
|
||||
case MOVED:
|
||||
state = DELETED;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
last_change = get_monotonic_time ();
|
||||
|
||||
file_changed ();
|
||||
}
|
||||
private void check_available () throws Error {
|
||||
if (build == null)
|
||||
throw new Error.NOT_SUPPORTED ("Compiler backend disabled at build-time");
|
||||
}
|
||||
|
||||
private enum FileState {
|
||||
PRISTINE,
|
||||
MODIFIED,
|
||||
DELETED,
|
||||
private BuildFunc? build;
|
||||
private WatchFunc? watch;
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate void BuildFunc (string project_root, string entrypoint, OutputFormat output_format,
|
||||
BundleFormat bundle_format, size_t disable_type_check, size_t source_map, size_t compress,
|
||||
owned BuildCompleteFunc on_complete, DiagnosticFunc on_diagnostic);
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate void WatchFunc (string project_root, string entrypoint, OutputFormat output_format,
|
||||
BundleFormat bundle_format, size_t disable_type_check, size_t source_map, size_t compress,
|
||||
owned WatchReadyFunc on_ready, StartingFunc on_starting, FinishedFunc on_finished, OutputFunc on_output,
|
||||
DiagnosticFunc on_diagnostic);
|
||||
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private extern void _init_go_runtime ();
|
||||
private extern void _build ();
|
||||
private extern void _watch ();
|
||||
#endif
|
||||
|
||||
namespace WatchSession {
|
||||
[CCode (has_target = false)]
|
||||
private delegate void DisposeFunc (size_t handle);
|
||||
|
||||
private DisposeFunc? dispose;
|
||||
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private extern void _dispose ();
|
||||
#endif
|
||||
}
|
||||
|
||||
private delegate void BuildCompleteFunc (owned string? bundle, owned string? error_message);
|
||||
private delegate void WatchReadyFunc (size_t session_handle, owned string? error_message);
|
||||
private delegate void StartingFunc ();
|
||||
private delegate void FinishedFunc ();
|
||||
private delegate void OutputFunc (owned string bundle);
|
||||
private delegate void DiagnosticFunc (owned string category, int code, owned string? path, int line, int character,
|
||||
owned string text);
|
||||
|
||||
#if HAVE_COMPILER_BACKEND && !COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private static T resolve_symbol<T> (Module m, string name) {
|
||||
void * address;
|
||||
if (!m.symbol (name, out address))
|
||||
assert_not_reached ();
|
||||
return (T) address;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private string compute_project_root (string entrypoint, CompilerOptions options) {
|
||||
string? project_root = options.project_root;
|
||||
|
||||
if (project_root != null)
|
||||
return project_root;
|
||||
|
||||
if (Path.is_absolute (entrypoint))
|
||||
return Path.get_dirname (entrypoint);
|
||||
|
||||
return Environment.get_current_dir ();
|
||||
}
|
||||
|
||||
public class CompilerOptions : Object {
|
||||
public string? project_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public OutputFormat output_format {
|
||||
get;
|
||||
set;
|
||||
default = UNESCAPED;
|
||||
}
|
||||
|
||||
public BundleFormat bundle_format {
|
||||
get;
|
||||
set;
|
||||
default = ESM;
|
||||
}
|
||||
|
||||
public TypeCheckMode type_check {
|
||||
get;
|
||||
set;
|
||||
default = FULL;
|
||||
}
|
||||
|
||||
public SourceMaps source_maps {
|
||||
get;
|
||||
set;
|
||||
@@ -465,10 +375,50 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class BuildOptions : CompilerOptions {
|
||||
public sealed class BuildOptions : CompilerOptions {
|
||||
}
|
||||
|
||||
public class WatchOptions : CompilerOptions {
|
||||
public sealed class WatchOptions : CompilerOptions {
|
||||
}
|
||||
|
||||
public enum OutputFormat {
|
||||
UNESCAPED,
|
||||
HEX_BYTES,
|
||||
C_STRING;
|
||||
|
||||
public static OutputFormat from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<OutputFormat> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<OutputFormat> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BundleFormat {
|
||||
ESM,
|
||||
IIFE;
|
||||
|
||||
public static BundleFormat from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<BundleFormat> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<BundleFormat> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TypeCheckMode {
|
||||
FULL,
|
||||
NONE;
|
||||
|
||||
public static TypeCheckMode from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<TypeCheckMode> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<TypeCheckMode> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SourceMaps {
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
INPUTS = [
|
||||
"agent-entrypoint.js",
|
||||
"agent-core.ts",
|
||||
"agent-warmup.js",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"tsconfig.json",
|
||||
"rollup.config.agent-core.ts",
|
||||
"rollup.config.typescript.ts",
|
||||
]
|
||||
|
||||
|
||||
def main(argv):
|
||||
output_dir, priv_dir, input_dir, npm, v8_mksnapshot = [Path(d).resolve() if d else None for d in argv[1:6]]
|
||||
host_os_family, host_arch, host_cpu_mode = argv[6:9]
|
||||
|
||||
try:
|
||||
generate_agent(output_dir, priv_dir, input_dir, npm, v8_mksnapshot, host_os_family, host_arch, host_cpu_mode)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e, file=sys.stderr)
|
||||
print("Output:\n\t| " + "\n\t| ".join(e.output.strip().split("\n")), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_agent(output_dir, priv_dir, input_dir, npm, v8_mksnapshot, host_os_family, host_arch, host_cpu_mode):
|
||||
entrypoint = input_dir / "agent-entrypoint.js"
|
||||
priv_dir.mkdir(exist_ok=True)
|
||||
for name in INPUTS:
|
||||
if name == "agent-entrypoint.js":
|
||||
continue
|
||||
shutil.copy(input_dir / name, priv_dir / name)
|
||||
|
||||
run_kwargs = {
|
||||
"cwd": priv_dir,
|
||||
"stdout": subprocess.PIPE,
|
||||
"stderr": subprocess.STDOUT,
|
||||
"encoding": "utf-8",
|
||||
"check": True,
|
||||
}
|
||||
|
||||
subprocess.run([npm, "install"], **run_kwargs)
|
||||
#subprocess.run([npm, "link", "/Users/oleavr/src/frida-compile"], **run_kwargs)
|
||||
|
||||
components = ["typescript", "agent-core"]
|
||||
for component in components:
|
||||
subprocess.run([
|
||||
npm, "run", "build:" + component,
|
||||
"--",
|
||||
"--environment", f"FRIDA_HOST_OS_FAMILY:{host_os_family},FRIDA_HOST_ARCH:{host_arch},FRIDA_HOST_CPU_MODE:{host_cpu_mode}",
|
||||
"--silent",
|
||||
],
|
||||
**run_kwargs)
|
||||
chunks = []
|
||||
for component in components:
|
||||
script = (priv_dir / f"{component}.js").read_text(encoding="utf-8")
|
||||
chunks.append(script)
|
||||
components_source = "\n".join(chunks)
|
||||
|
||||
agent = output_dir / "agent.js"
|
||||
snapshot = output_dir / "snapshot.bin"
|
||||
|
||||
if v8_mksnapshot is not None:
|
||||
shutil.copy(entrypoint, agent)
|
||||
(priv_dir / "embed.js").write_text(components_source, encoding="utf-8")
|
||||
subprocess.run([
|
||||
v8_mksnapshot,
|
||||
"--turbo-instruction-scheduling",
|
||||
f"--startup-blob={snapshot}",
|
||||
"embed.js",
|
||||
input_dir / "agent-warmup.js",
|
||||
],
|
||||
**run_kwargs)
|
||||
else:
|
||||
agent.write_text("\n".join([
|
||||
components_source,
|
||||
entrypoint.read_text(encoding="utf-8"),
|
||||
]), encoding="utf-8")
|
||||
snapshot.write_bytes(b"")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,14 @@
|
||||
module github.com/frida/frida-core-compiler
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/evanw/esbuild v0.25.5
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/evanw/esbuild v0.25.5 h1:E+JpeY5S/1LFmnX1vtuZqUKT7qDVcfXdhzMhM3uIKFs=
|
||||
github.com/evanw/esbuild v0.25.5/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c h1:IWBI9AwSFLWymI4enx1jrAw0E4H2E+qHwsihv+AxSoE=
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c/go.mod h1:L4RMzD/pFBl7CqIf9XTrX41RO2xcok3OOVVcowv0H4k=
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 h1:o8UqXPI6SVwQt04RGsqKp3qqmbOfTNMqDrWsc4O47kk=
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
|
||||
github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
+45
-41
@@ -1,61 +1,65 @@
|
||||
compiler_sources = files(
|
||||
'compiler.vala',
|
||||
'backend-glue.c',
|
||||
)
|
||||
|
||||
if have_compiler_backend
|
||||
compiler_mksnapshot = generate_compiler_snapshot ? v8_mksnapshot : ''
|
||||
|
||||
compiler_agent = custom_target('frida-compiler-agent',
|
||||
compiler_backend = custom_target('frida-compiler-backend',
|
||||
input: [
|
||||
'agent-entrypoint.js',
|
||||
'agent-core.ts',
|
||||
'agent-warmup.js',
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
'backend.go',
|
||||
'backend.version',
|
||||
'tscompiler.go',
|
||||
'tsconfig.go',
|
||||
'shims.go',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'tsconfig.json',
|
||||
'rollup.config.agent-core.ts',
|
||||
'rollup.config.typescript.ts',
|
||||
],
|
||||
output: [
|
||||
'agent.js',
|
||||
'snapshot.bin',
|
||||
'frida-compiler-backend' + (compiler_backend_static_compilation ? '.a' : ('-raw' + shlib_suffix)),
|
||||
'frida-compiler-backend.h',
|
||||
],
|
||||
command: [
|
||||
python,
|
||||
files('generate-agent.py'),
|
||||
files('build-backend.py'),
|
||||
go_config,
|
||||
meson.current_build_dir(),
|
||||
'@PRIVATE_DIR@',
|
||||
meson.current_source_dir(),
|
||||
go,
|
||||
npm,
|
||||
compiler_mksnapshot,
|
||||
host_os_family,
|
||||
host_arch_gumjs,
|
||||
host_cpu_mode,
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources.' + frida_profile,
|
||||
compiler_agent,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
'frida-data-compiler-agent.js.br',
|
||||
'frida-data-compiler-snapshot.bin.br',
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
'@INPUT2@',
|
||||
],
|
||||
)
|
||||
compiler_sources += compiler_data
|
||||
if compiler_backend_static_compilation
|
||||
backend_libs += compiler_backend[0]
|
||||
else
|
||||
filename = 'frida-compiler-backend' + shlib_suffix
|
||||
identity = (host_os_family == 'darwin') ? 'FridaCompilerBackend' : filename
|
||||
compiler_backend_shlib = custom_target('frida-compiler-backend-shlib',
|
||||
input: compiler_backend[0],
|
||||
output: filename,
|
||||
command: post_process + ['shared-library', identity, 'elf-cleaner:off'],
|
||||
)
|
||||
|
||||
backend_vala_args_private += '--pkg=libbrotlidec'
|
||||
backend_deps_private += brotlidec_dep
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources',
|
||||
compiler_backend_shlib,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += compiler_data
|
||||
endif
|
||||
endif
|
||||
Generated
+105
-1746
File diff suppressed because it is too large.
Load diff
+31
-22
@@ -1,27 +1,36 @@
|
||||
{
|
||||
"name": "frida-compiler-agent",
|
||||
"name": "frida-compiler-shims",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent used by Frida's compiler",
|
||||
"main": "agent.ts",
|
||||
"scripts": {
|
||||
"build:typescript": "rollup --config rollup.config.typescript.ts --configPlugin typescript",
|
||||
"build:agent-core": "rollup --config rollup.config.agent-core.ts --configPlugin typescript"
|
||||
},
|
||||
"description": "Shims used by Frida's compiler",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@frida/crosspath": "3.0.0",
|
||||
"frida-compile": "16.4.1",
|
||||
"frida-fs": "5.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@frida/rollup-plugin-node-polyfills": "^1.0.3",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@types/frida-gum": "18.8.1",
|
||||
"@types/glob": "^8.0.0",
|
||||
"@types/node": "18.7.23",
|
||||
"glob": "^8.0.3",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
"@frida/assert": "^3.0.2",
|
||||
"@frida/base64-js": "^2.0.3",
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"@frida/crypto": "^1.0.1",
|
||||
"@frida/diagnostics_channel": "^1.0.0",
|
||||
"@frida/events": "^4.0.4",
|
||||
"@frida/http": "^4.0.2",
|
||||
"@frida/http-parser-js": "^1.0.0",
|
||||
"@frida/https": "^1.0.0",
|
||||
"@frida/ieee754": "^2.0.2",
|
||||
"@frida/net": "^5.0.0",
|
||||
"@frida/os": "^1.0.2",
|
||||
"@frida/path": "^2.0.3",
|
||||
"@frida/process": "^1.2.1",
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0",
|
||||
"@frida/readable-stream": "^4.1.3",
|
||||
"@frida/reserved-words": "^1.0.0",
|
||||
"@frida/stream": "^1.0.2",
|
||||
"@frida/string_decoder": "^2.0.0",
|
||||
"@frida/timers": "^3.0.0",
|
||||
"@frida/tty": "^1.0.0",
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "19.0.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
import crosspath from "@frida/crosspath";
|
||||
import fs from "fs";
|
||||
import { sync as glob } from "glob";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import polyfills from "@frida/rollup-plugin-node-polyfills";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import { defineConfig } from "rollup";
|
||||
import type rollup from "rollup";
|
||||
|
||||
export default defineConfig({
|
||||
input: "agent-core.ts",
|
||||
output: {
|
||||
file: "agent-core.js",
|
||||
format: "umd",
|
||||
name: "FridaCompilerAgentCore",
|
||||
generatedCode: {
|
||||
preset: "es2015",
|
||||
},
|
||||
strict: false,
|
||||
interop: "default",
|
||||
globals: {
|
||||
"typescript": "ts",
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
typescript(),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: computeSubstitutionValues(),
|
||||
}),
|
||||
stubTsImportPlugin(),
|
||||
polyfills(),
|
||||
resolve(),
|
||||
/*
|
||||
terser({
|
||||
ecma: 2020,
|
||||
compress: {
|
||||
module: true,
|
||||
global_defs: {
|
||||
"process.env.FRIDA_COMPILE": true
|
||||
},
|
||||
},
|
||||
mangle: {
|
||||
module: true,
|
||||
},
|
||||
}),
|
||||
*/
|
||||
],
|
||||
});
|
||||
|
||||
function computeSubstitutionValues() {
|
||||
const hostOsFamily = process.env.FRIDA_HOST_OS_FAMILY;
|
||||
if (hostOsFamily === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_OS_FAMILY");
|
||||
}
|
||||
|
||||
const hostArch = process.env.FRIDA_HOST_ARCH;
|
||||
if (hostArch === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_ARCH");
|
||||
}
|
||||
|
||||
const hostCpuMode = process.env.FRIDA_HOST_CPU_MODE;
|
||||
if (hostCpuMode === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_CPU_MODE");
|
||||
}
|
||||
|
||||
const outputDir = __dirname;
|
||||
|
||||
const compilerDir = crosspath.join(__dirname, "node_modules", "frida-compile");
|
||||
let usingLinkedCompiler = false;
|
||||
try {
|
||||
usingLinkedCompiler = fs.statSync(crosspath.join(compilerDir, "node_modules")).isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
const assetParentDir = usingLinkedCompiler ? compilerDir : outputDir;
|
||||
const assetModulesDir = crosspath.join(assetParentDir, "node_modules");
|
||||
|
||||
const shimDirs = [
|
||||
["@frida", "assert"],
|
||||
["@frida", "base64-js"],
|
||||
["@frida", "buffer"],
|
||||
["@frida", "crypto"],
|
||||
["@frida", "diagnostics_channel"],
|
||||
["@frida", "events"],
|
||||
["@frida", "http"],
|
||||
["@frida", "http-parser-js"],
|
||||
["@frida", "https"],
|
||||
["@frida", "ieee754"],
|
||||
["@frida", "net"],
|
||||
["@frida", "os"],
|
||||
["@frida", "path"],
|
||||
["@frida", "process"],
|
||||
["@frida", "punycode"],
|
||||
["@frida", "querystring"],
|
||||
["@frida", "readable-stream"],
|
||||
["@frida", "stream"],
|
||||
["@frida", "string_decoder"],
|
||||
["@frida", "timers"],
|
||||
["@frida", "tty"],
|
||||
["@frida", "url"],
|
||||
["@frida", "util"],
|
||||
["@frida", "vm"],
|
||||
["frida-fs"],
|
||||
];
|
||||
const typeDirs = [
|
||||
["@types", "node"],
|
||||
["@types", "frida-gum"],
|
||||
];
|
||||
|
||||
const assets: string[] = [];
|
||||
for (const shimDir of shimDirs) {
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...shimDir, "package.json")));
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...shimDir, "**", "*.js")));
|
||||
}
|
||||
for (const typeDir of typeDirs) {
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...typeDir, "package.json")));
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...typeDir, "**", "*.d.ts")));
|
||||
}
|
||||
assets.push(...glob(crosspath.join(compilerDir, "ext", "lib.es*.d.ts")));
|
||||
assets.push(...glob(crosspath.join(compilerDir, "ext", "lib.decorators*.d.ts")));
|
||||
|
||||
const ignoredAssetFiles = new Set([
|
||||
"@frida/process/browser.js",
|
||||
]);
|
||||
|
||||
const agentDirectories = new Set<string>();
|
||||
const agentFiles = new Map<string, string>();
|
||||
for (const assetPath of assets) {
|
||||
let assetRelpath = assetPath.substring(assetParentDir.length);
|
||||
if (usingLinkedCompiler && assetRelpath.startsWith("/ext/")) {
|
||||
assetRelpath = crosspath.join("/", "node_modules", "frida-compile", assetRelpath.substring(1));
|
||||
}
|
||||
|
||||
const identifier = assetRelpath.split("/").slice(2).join("/");
|
||||
if (ignoredAssetFiles.has(identifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let currentDir = crosspath.dirname(assetRelpath);
|
||||
do {
|
||||
agentDirectories.add(currentDir);
|
||||
currentDir = crosspath.dirname(currentDir);
|
||||
} while (currentDir !== "/");
|
||||
agentFiles.set(assetRelpath, fs.readFileSync(assetPath, "utf-8"));
|
||||
}
|
||||
|
||||
const orderedAgentDirectories = Array.from(agentDirectories);
|
||||
orderedAgentDirectories.sort();
|
||||
|
||||
const orderedAgentFiles = Array.from(agentFiles);
|
||||
orderedAgentFiles.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
return {
|
||||
"Frida.version": "'0.0.0'",
|
||||
"Process.id": "1",
|
||||
"Process.platform": `'${hostOsFamily}'`,
|
||||
"Process.arch": `'${hostArch}'`,
|
||||
"Process.pointerSize": (hostCpuMode === "64") ? "8" : "4",
|
||||
"__agentDirectories__": JSON.stringify(orderedAgentDirectories, null, 4),
|
||||
"__agentFiles__": JSON.stringify(orderedAgentFiles, null, 4),
|
||||
};
|
||||
}
|
||||
|
||||
function stubTsImportPlugin(): rollup.Plugin {
|
||||
return {
|
||||
name: "stub-ts-import",
|
||||
async resolveId(source, importer, options) {
|
||||
if (crosspath.basename(source) === "typescript.js") {
|
||||
return {
|
||||
id: "typescript",
|
||||
external: true,
|
||||
meta: {},
|
||||
moduleSideEffects: false,
|
||||
syntheticNamedExports: false
|
||||
};
|
||||
}
|
||||
|
||||
return this.resolve(source, importer, { skipSelf: true, ...options });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "rollup";
|
||||
|
||||
export default defineConfig({
|
||||
input: path.join("node_modules", "frida-compile", "ext", "typescript.js"),
|
||||
output: {
|
||||
file: "typescript.js",
|
||||
format: "umd",
|
||||
name: "ts",
|
||||
generatedCode: {
|
||||
preset: "es2015",
|
||||
},
|
||||
strict: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
var nodeGlobals = `
|
||||
export { Buffer } from 'node:buffer';
|
||||
export { default as process } from 'node:process';
|
||||
`
|
||||
|
||||
//go:embed node_modules/@frida/*/package.json
|
||||
//go:embed node_modules/@frida/*/*.js
|
||||
//go:embed node_modules/@frida/*/*/*.js
|
||||
//go:embed node_modules/@frida/*/*/*/*.js
|
||||
//go:embed node_modules/frida-fs/package.json
|
||||
//go:embed node_modules/frida-fs/*/*.js
|
||||
var embeddedShims embed.FS
|
||||
|
||||
func makeFridaShimsPlugin() esbuild.Plugin {
|
||||
const (
|
||||
nsBuiltins = "frida-builtins"
|
||||
nsShim = "frida-shim"
|
||||
)
|
||||
|
||||
const (
|
||||
builtinFilter = `^frida-builtins://(.+)$`
|
||||
shimFilter = `^(assert|base64-js|buffer|crypto|diagnostics_channel|events|fs|http|https|http-parser-js|ieee754|net|os|path|process|punycode|querystring|readable-stream|stream|string_decoder|timers|tty|url|util|vm)$`
|
||||
shimNodePrefixFilter = `^node:(assert|buffer|crypto|diagnostics_channel|events|fs|http|https|net|os|path|process|punycode|querystring|stream|string_decoder|timers|tty|url|util|vm)$`
|
||||
)
|
||||
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-custom-shims",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: builtinFilter},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return esbuild.OnResolveResult{
|
||||
Path: strings.TrimPrefix(args.Path, "frida-builtins://"),
|
||||
Namespace: nsBuiltins,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: ".*", Namespace: nsBuiltins},
|
||||
func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
if args.Path != "/node-globals.js" {
|
||||
return esbuild.OnLoadResult{
|
||||
Errors: []esbuild.Message{{Text: "Unexpected path: " + args.Path}},
|
||||
}, nil
|
||||
}
|
||||
return esbuild.OnLoadResult{
|
||||
Contents: &nodeGlobals,
|
||||
Loader: esbuild.LoaderJS,
|
||||
}, nil
|
||||
})
|
||||
|
||||
registerShimResolve := func(pattern string) {
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: pattern},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return makeResolveResult(nsShim, args.Path)
|
||||
})
|
||||
}
|
||||
registerShimResolve(shimFilter)
|
||||
registerShimResolve(shimNodePrefixFilter)
|
||||
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: ".*", Namespace: nsShim},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
if strings.HasPrefix(args.Path, ".") {
|
||||
abs := filepath.ToSlash(filepath.Join(filepath.Dir(args.Importer), args.Path))
|
||||
return esbuild.OnResolveResult{
|
||||
Path: abs,
|
||||
Namespace: nsShim,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
}
|
||||
return makeResolveResult(nsShim, args.Path)
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: ".*", Namespace: nsShim},
|
||||
func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
data, err := embeddedShims.ReadFile(filepath.ToSlash(args.Path))
|
||||
if err != nil {
|
||||
return esbuild.OnLoadResult{
|
||||
Errors: []esbuild.Message{{
|
||||
Text: fmt.Sprintf("Error reading shim file %q: %v", args.Path, err),
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
return esbuild.OnLoadResult{
|
||||
Contents: &content,
|
||||
Loader: loaderFor(args.Path),
|
||||
}, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeResolveResult(ns string, module string) (esbuild.OnResolveResult, error) {
|
||||
path, errs := resolveShim(module)
|
||||
if len(errs) > 0 {
|
||||
return esbuild.OnResolveResult{Errors: errs}, nil
|
||||
}
|
||||
return esbuild.OnResolveResult{
|
||||
Path: path,
|
||||
Namespace: ns,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loaderFor(path string) esbuild.Loader {
|
||||
switch ext := filepath.Ext(path); ext {
|
||||
case ".js", ".mjs", ".cjs":
|
||||
return esbuild.LoaderJS
|
||||
case ".json":
|
||||
return esbuild.LoaderJSON
|
||||
default:
|
||||
panic(fmt.Sprintf("frida-shims: unsupported file type %q", ext))
|
||||
}
|
||||
}
|
||||
|
||||
var shimMap = map[string]string{
|
||||
"assert": "@frida/assert",
|
||||
"base64-js": "@frida/base64-js",
|
||||
"buffer": "@frida/buffer",
|
||||
"crypto": "@frida/crypto",
|
||||
"diagnostics_channel": "@frida/diagnostics_channel",
|
||||
"events": "@frida/events",
|
||||
"fs": "frida-fs",
|
||||
"http": "@frida/http",
|
||||
"https": "@frida/https",
|
||||
"http-parser-js": "@frida/http-parser-js",
|
||||
"ieee754": "@frida/ieee754",
|
||||
"net": "@frida/net",
|
||||
"os": "@frida/os",
|
||||
"path": "@frida/path",
|
||||
"process": "@frida/process",
|
||||
"punycode": "@frida/punycode",
|
||||
"querystring": "@frida/querystring",
|
||||
"readable-stream": "@frida/readable-stream",
|
||||
"stream": "@frida/stream",
|
||||
"string_decoder": "@frida/string_decoder",
|
||||
"timers": "@frida/timers",
|
||||
"tty": "@frida/tty",
|
||||
"url": "@frida/url",
|
||||
"util": "@frida/util",
|
||||
"vm": "@frida/vm",
|
||||
}
|
||||
|
||||
type PackageJSON struct {
|
||||
Main string `json:"main"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
func resolveShim(shimName string) (string, []esbuild.Message) {
|
||||
var subDir string
|
||||
if strings.HasPrefix(shimName, "@frida/") {
|
||||
subDir = shimName
|
||||
} else {
|
||||
actualShimName := strings.TrimPrefix(shimName, "node:")
|
||||
|
||||
var ok bool
|
||||
subDir, ok = shimMap[actualShimName]
|
||||
if !ok {
|
||||
return "", []esbuild.Message{{Text: "Unknown shim: " + actualShimName}}
|
||||
}
|
||||
}
|
||||
|
||||
shimPackageDir := filepath.Join("node_modules", subDir)
|
||||
packageJSONPath := filepath.Join(shimPackageDir, "package.json")
|
||||
|
||||
pkgJSONBytes, _ := embeddedShims.ReadFile(filepath.ToSlash(packageJSONPath))
|
||||
|
||||
var pkg PackageJSON
|
||||
json.Unmarshal(pkgJSONBytes, &pkg)
|
||||
|
||||
entryFile := ""
|
||||
if pkg.Module != "" {
|
||||
entryFile = pkg.Module
|
||||
} else if pkg.Main != "" {
|
||||
entryFile = pkg.Main
|
||||
} else {
|
||||
entryFile = "index.js"
|
||||
}
|
||||
|
||||
resolvedEntryPath := filepath.Join(shimPackageDir, entryFile)
|
||||
|
||||
return filepath.ToSlash(resolvedEntryPath), nil
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/frida/typescript-go/pkg/ast"
|
||||
"github.com/frida/typescript-go/pkg/bundled"
|
||||
"github.com/frida/typescript-go/pkg/compiler"
|
||||
"github.com/frida/typescript-go/pkg/core"
|
||||
"github.com/frida/typescript-go/pkg/tsoptions"
|
||||
"github.com/frida/typescript-go/pkg/tspath"
|
||||
"github.com/frida/typescript-go/pkg/vfs"
|
||||
"github.com/frida/typescript-go/pkg/vfs/iovfs"
|
||||
"github.com/frida/typescript-go/pkg/vfs/osvfs"
|
||||
)
|
||||
|
||||
//go:embed node_modules/@types/*/package.json
|
||||
//go:embed node_modules/@types/*/*.d.ts
|
||||
//go:embed node_modules/@types/*/*/*.d.ts
|
||||
var embeddedTypes embed.FS
|
||||
|
||||
type TSCompiler struct {
|
||||
projectRoot string
|
||||
entrypoint string
|
||||
loadCompilerOptions LoadCompilerOptionsHandler
|
||||
fs vfs.FS
|
||||
captureFs *captureFS
|
||||
|
||||
mu sync.Mutex
|
||||
options *core.CompilerOptions
|
||||
program *compiler.Program
|
||||
programErr error
|
||||
forceFreshProgram bool
|
||||
mtimes map[tspath.Path]time.Time
|
||||
inputDirs, inputFiles []string
|
||||
pendingDirs, pendingFiles []string
|
||||
}
|
||||
|
||||
type LoadCompilerOptionsHandler func(host tsoptions.ParseConfigHost) (*core.CompilerOptions, string, error)
|
||||
|
||||
func NewTSCompiler(projectRoot, entrypoint string, loadCompilerOptions LoadCompilerOptionsHandler) *TSCompiler {
|
||||
captureFs := newCaptureFS(osvfs.FS())
|
||||
fs := newTypesFS(bundled.WrapFS(captureFs), projectRoot)
|
||||
|
||||
return &TSCompiler{
|
||||
projectRoot: projectRoot,
|
||||
entrypoint: tspath.NormalizePath(entrypoint),
|
||||
loadCompilerOptions: loadCompilerOptions,
|
||||
fs: fs,
|
||||
captureFs: captureFs,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TSCompiler) resetProgramState() {
|
||||
c.options = nil
|
||||
c.program = nil
|
||||
c.programErr = nil
|
||||
c.forceFreshProgram = false
|
||||
c.mtimes = nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) EnsureProgramUpToDate() error {
|
||||
opts, _, err := c.loadCompilerOptions(c)
|
||||
if err != nil {
|
||||
c.resetProgramState()
|
||||
c.recomputeInputs()
|
||||
return err
|
||||
}
|
||||
|
||||
var prog *compiler.Program
|
||||
var progErr error
|
||||
|
||||
if c.forceFreshProgram || c.program == nil || opts != c.options {
|
||||
c.forceFreshProgram = false
|
||||
prog, progErr = c.createProgram(opts)
|
||||
} else {
|
||||
prog, progErr = c.updateProgram(c.program, opts)
|
||||
}
|
||||
|
||||
c.program = prog
|
||||
c.programErr = progErr
|
||||
c.options = opts
|
||||
|
||||
c.recomputeInputs()
|
||||
|
||||
return progErr
|
||||
}
|
||||
|
||||
func (c *TSCompiler) createProgram(options *core.CompilerOptions) (*compiler.Program, error) {
|
||||
host := compiler.NewCompilerHost(options, c.projectRoot, c.fs, bundled.LibPath())
|
||||
|
||||
program := compiler.NewProgram(compiler.ProgramOptions{
|
||||
RootFiles: []string{c.entrypoint},
|
||||
Host: host,
|
||||
Options: options,
|
||||
})
|
||||
|
||||
c.updateMtimes(program)
|
||||
|
||||
return program, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) updateProgram(old *compiler.Program, options *core.CompilerOptions) (*compiler.Program, error) {
|
||||
newMtimes, err := c.collectMtimes(old)
|
||||
if err != nil {
|
||||
return c.createProgram(options)
|
||||
}
|
||||
|
||||
updated := []tspath.Path{}
|
||||
newProg := old
|
||||
for path, mtime := range newMtimes {
|
||||
if !mtime.Equal(c.mtimes[path]) {
|
||||
var reused bool
|
||||
newProg, reused = newProg.UpdateProgram(path)
|
||||
updated = append(updated, path)
|
||||
if !reused {
|
||||
c.updateMtimes(newProg)
|
||||
return newProg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
c.mtimes = newMtimes
|
||||
return newProg, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) updateMtimes(p *compiler.Program) {
|
||||
mt, _ := c.collectMtimes(p)
|
||||
c.mtimes = mt
|
||||
}
|
||||
|
||||
func (c *TSCompiler) collectMtimes(p *compiler.Program) (map[tspath.Path]time.Time, error) {
|
||||
mt := make(map[tspath.Path]time.Time, len(p.SourceFiles()))
|
||||
|
||||
for _, sf := range p.SourceFiles() {
|
||||
name := sf.FileName()
|
||||
if isBundled(name) {
|
||||
continue
|
||||
}
|
||||
info := c.fs.Stat(name)
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("File %q disappeared", name)
|
||||
}
|
||||
mt[sf.Path()] = info.ModTime()
|
||||
}
|
||||
|
||||
return mt, nil
|
||||
}
|
||||
|
||||
func isBundled(name string) bool {
|
||||
return strings.HasPrefix(name, "bundled://")
|
||||
}
|
||||
|
||||
func (c *TSCompiler) WatchDirs() []string {
|
||||
if c.inputDirs != nil {
|
||||
return c.inputDirs
|
||||
}
|
||||
return c.pendingDirs
|
||||
}
|
||||
|
||||
func (c *TSCompiler) WatchFiles() []string {
|
||||
if c.inputFiles != nil {
|
||||
return c.inputFiles
|
||||
}
|
||||
return c.pendingFiles
|
||||
}
|
||||
|
||||
func (c *TSCompiler) CommitPendingInputs() {
|
||||
c.inputDirs, c.pendingDirs = c.pendingDirs, nil
|
||||
c.inputFiles, c.pendingFiles = c.pendingFiles, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) DropPendingInputs() {
|
||||
c.pendingDirs, c.pendingFiles = nil, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) recomputeInputs() {
|
||||
var capHint int
|
||||
if c.program != nil {
|
||||
capHint = 1 + len(c.program.GetSourceFiles())
|
||||
} else {
|
||||
capHint = 1
|
||||
}
|
||||
|
||||
files := make([]string, 0, capHint)
|
||||
files = append(files, filepath.Join(c.projectRoot, "tsconfig.json"))
|
||||
if c.program != nil {
|
||||
for _, sf := range c.program.GetSourceFiles() {
|
||||
name := sf.FileName()
|
||||
if !isBundled(name) {
|
||||
files = append(files, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
c.pendingDirs = uniqueDirs(files)
|
||||
c.pendingFiles = files
|
||||
}
|
||||
|
||||
func (c *TSCompiler) Compile(filePathToCompile string) (string, []*ast.Diagnostic, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.programErr != nil {
|
||||
return "", nil, c.programErr
|
||||
}
|
||||
program := c.program
|
||||
|
||||
var targetSourceFile *ast.SourceFile
|
||||
normalizedFilePathToCompile := tspath.NormalizePath(filePathToCompile)
|
||||
for _, sf := range program.GetSourceFiles() {
|
||||
if sf.FileName() == normalizedFilePathToCompile {
|
||||
targetSourceFile = sf
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetSourceFile == nil {
|
||||
return "", nil, fmt.Errorf("TypeScript source file not found in program: %s", filePathToCompile)
|
||||
}
|
||||
|
||||
c.captureFs.ClearOutputs()
|
||||
|
||||
res := program.Emit(compiler.EmitOptions{
|
||||
TargetSourceFile: targetSourceFile,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
diagnostics := program.GetSyntacticDiagnostics(ctx, targetSourceFile)
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetBindDiagnostics(ctx, targetSourceFile)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetOptionsDiagnostics(ctx)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetGlobalDiagnostics(ctx)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
semanticDiags := program.GetSemanticDiagnostics(ctx, targetSourceFile)
|
||||
for _, d := range semanticDiags {
|
||||
switch d.Code() {
|
||||
case
|
||||
2305, // Module '{0}' has no exported member '{1}'.
|
||||
2306, // File '{0}' is not a module.
|
||||
2307: // Cannot find module '{0}' or its corresponding type declarations.
|
||||
c.forceFreshProgram = true
|
||||
}
|
||||
}
|
||||
diagnostics = append(diagnostics, semanticDiags...)
|
||||
}
|
||||
|
||||
if res.EmitSkipped {
|
||||
errMsg := "TypeScript compilation failed and was skipped"
|
||||
if len(diagnostics) == 0 {
|
||||
errMsg = fmt.Sprintf("TypeScript compilation failed and was skipped for %s, but no diagnostics reported", filePathToCompile)
|
||||
}
|
||||
return "", diagnostics, fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
var compiledJS string
|
||||
var foundJSOutput bool
|
||||
for path, content := range c.captureFs.GetOutputs() {
|
||||
ext := filepath.Ext(path)
|
||||
if ext == ".js" {
|
||||
compiledJS = content
|
||||
foundJSOutput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundJSOutput {
|
||||
if len(diagnostics) == 0 {
|
||||
return "", nil, fmt.Errorf("TypeScript compilation for %s seemed to succeed (emit not skipped, no diagnostics) but no .js output file was captured. Captured outputs: %v", filePathToCompile, c.captureFs.GetOutputs())
|
||||
}
|
||||
return "", diagnostics, fmt.Errorf("No .js output file was captured for %s. Captured outputs: %v", filePathToCompile, c.captureFs.GetOutputs())
|
||||
}
|
||||
|
||||
return compiledJS, diagnostics, nil
|
||||
}
|
||||
|
||||
func uniqueDirs(files []string) []string {
|
||||
m := make(map[string]struct{}, len(files))
|
||||
for _, f := range files {
|
||||
m[filepath.Dir(f)] = struct{}{}
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(m))
|
||||
for d := range m {
|
||||
out = append(out, d)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// FS implements ParseConfigHost.
|
||||
func (c *TSCompiler) FS() vfs.FS {
|
||||
return c.fs
|
||||
}
|
||||
|
||||
// GetCurrentDirectory implements ParseConfigHost.
|
||||
func (c *TSCompiler) GetCurrentDirectory() string {
|
||||
return c.projectRoot
|
||||
}
|
||||
|
||||
type captureFS struct {
|
||||
vfs.FS
|
||||
outputs map[string]string
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
var _ vfs.FS = (*captureFS)(nil)
|
||||
|
||||
func newCaptureFS(inner vfs.FS) *captureFS {
|
||||
return &captureFS{
|
||||
FS: inner,
|
||||
outputs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *captureFS) ClearOutputs() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.outputs = make(map[string]string)
|
||||
}
|
||||
|
||||
func (c *captureFS) GetOutputs() map[string]string {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
snap := make(map[string]string, len(c.outputs))
|
||||
for k, v := range c.outputs {
|
||||
snap[k] = v
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
func (c *captureFS) WriteFile(path string, data string, writeByteOrderMark bool) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.outputs[path] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *captureFS) Remove(path string) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
delete(c.outputs, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
type typesFS struct {
|
||||
vfs.FS
|
||||
projectRoot string
|
||||
types vfs.FS
|
||||
}
|
||||
|
||||
var _ vfs.FS = (*typesFS)(nil)
|
||||
|
||||
func newTypesFS(inner vfs.FS, projectRoot string) *typesFS {
|
||||
return &typesFS{
|
||||
FS: inner,
|
||||
projectRoot: projectRoot,
|
||||
types: iovfs.From(embeddedTypes, true),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *typesFS) DirectoryExists(path string) bool {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
if t.types.DirectoryExists(embeddedPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return t.FS.DirectoryExists(path)
|
||||
}
|
||||
|
||||
func (t *typesFS) GetAccessibleEntries(path string) vfs.Entries {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
|
||||
var embeddedEntries vfs.Entries
|
||||
if embeddedPath != "" {
|
||||
embeddedEntries = t.types.GetAccessibleEntries(embeddedPath)
|
||||
}
|
||||
|
||||
otherEntries := t.FS.GetAccessibleEntries(path)
|
||||
|
||||
return vfs.Entries{
|
||||
Files: mergeAndSort(embeddedEntries.Files, otherEntries.Files),
|
||||
Directories: mergeAndSort(embeddedEntries.Directories, otherEntries.Directories),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *typesFS) FileExists(path string) bool {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
if t.types.FileExists(embeddedPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return t.FS.FileExists(path)
|
||||
}
|
||||
|
||||
func (t *typesFS) ReadFile(path string) (contents string, ok bool) {
|
||||
contents, ok = t.FS.ReadFile(path)
|
||||
if ok {
|
||||
return contents, ok
|
||||
}
|
||||
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
contents, ok = t.types.ReadFile(embeddedPath)
|
||||
}
|
||||
|
||||
return contents, ok
|
||||
}
|
||||
|
||||
func (c *typesFS) resolveEmbeddedPath(path string) string {
|
||||
rel, err := filepath.Rel(c.projectRoot, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "/" + filepath.ToSlash(rel)
|
||||
}
|
||||
|
||||
func mergeAndSort(a, b []string) []string {
|
||||
m := make(map[string]struct{}, len(a)+len(b))
|
||||
for _, v := range append(a, b...) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return slices.Sorted(maps.Keys(m))
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tscore "github.com/frida/typescript-go/pkg/core"
|
||||
"github.com/frida/typescript-go/pkg/tsoptions"
|
||||
"github.com/frida/typescript-go/pkg/tspath"
|
||||
)
|
||||
|
||||
type TSConfigCache struct {
|
||||
projectRoot string
|
||||
sourceMap bool
|
||||
tsconfigPath string
|
||||
onChange ConfigChangeCallback
|
||||
|
||||
mu sync.Mutex
|
||||
lastModTime time.Time
|
||||
cachedOpts *tscore.CompilerOptions
|
||||
lastText string
|
||||
}
|
||||
|
||||
type ConfigChangeCallback func()
|
||||
|
||||
func NewTSConfigCache(
|
||||
projectRoot string,
|
||||
sourceMap bool,
|
||||
callback ConfigChangeCallback,
|
||||
) *TSConfigCache {
|
||||
return &TSConfigCache{
|
||||
projectRoot: projectRoot,
|
||||
sourceMap: sourceMap,
|
||||
tsconfigPath: filepath.Join(projectRoot, "tsconfig.json"),
|
||||
lastModTime: time.Time{},
|
||||
cachedOpts: nil,
|
||||
lastText: "",
|
||||
onChange: callback,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TSConfigCache) GetCompilerOptions(
|
||||
host tsoptions.ParseConfigHost,
|
||||
) (*tscore.CompilerOptions, string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
fs := host.FS()
|
||||
|
||||
info := fs.Stat(c.tsconfigPath)
|
||||
var currentModTime time.Time
|
||||
if info != nil {
|
||||
currentModTime = info.ModTime()
|
||||
} else {
|
||||
currentModTime = time.Time{}
|
||||
}
|
||||
|
||||
if c.cachedOpts != nil && currentModTime.Equal(c.lastModTime) {
|
||||
return c.cachedOpts, c.lastText, nil
|
||||
}
|
||||
|
||||
var tsconfigText string
|
||||
if info != nil {
|
||||
data, ok := fs.ReadFile(c.tsconfigPath)
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("Unable to read %q", c.tsconfigPath)
|
||||
}
|
||||
tsconfigText = string(data)
|
||||
} else {
|
||||
tsconfigText = "{}"
|
||||
}
|
||||
|
||||
parsedSource := tsoptions.NewTsconfigSourceFileFromFilePath(
|
||||
tspath.NormalizePath(c.tsconfigPath),
|
||||
tspath.ToPath(c.tsconfigPath, "", fs.UseCaseSensitiveFileNames()),
|
||||
tsconfigText,
|
||||
)
|
||||
parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
|
||||
parsedSource,
|
||||
host,
|
||||
c.projectRoot,
|
||||
nil,
|
||||
c.tsconfigPath,
|
||||
nil, nil, nil,
|
||||
)
|
||||
|
||||
if len(parsedCommandLine.Errors) > 0 {
|
||||
var msgs []string
|
||||
for _, diag := range parsedCommandLine.Errors {
|
||||
msgs = append(msgs, diag.Message())
|
||||
}
|
||||
return nil, "", fmt.Errorf(
|
||||
"Failed to parse %s: %s",
|
||||
c.tsconfigPath,
|
||||
strings.Join(msgs, "; "),
|
||||
)
|
||||
}
|
||||
|
||||
newOpts := parsedCommandLine.CompilerOptions()
|
||||
newOpts.Module = tscore.ModuleKindNode16
|
||||
newOpts.ModuleResolution = tscore.ModuleResolutionKindNode16
|
||||
newOpts.NoEmit = tscore.TSFalse
|
||||
|
||||
sourceMapOptVal := boolToTristate(c.sourceMap)
|
||||
newOpts.SourceMap = sourceMapOptVal
|
||||
newOpts.InlineSourceMap = sourceMapOptVal
|
||||
|
||||
if newOpts.Target == tscore.ScriptTargetNone {
|
||||
newOpts.Target = tscore.ScriptTargetES2022
|
||||
}
|
||||
if newOpts.Lib == nil {
|
||||
newOpts.Lib = []string{"lib.es2022.d.ts"}
|
||||
}
|
||||
if newOpts.SkipLibCheck == tscore.TSUnknown {
|
||||
newOpts.SkipLibCheck = tscore.TSTrue
|
||||
}
|
||||
if newOpts.Strict == tscore.TSUnknown {
|
||||
newOpts.Strict = tscore.TSTrue
|
||||
}
|
||||
|
||||
tsconfigData, _ := json.Marshal(parsedCommandLine.ParsedConfig)
|
||||
tsconfigText = string(tsconfigData)
|
||||
|
||||
hadPreviousOpts := c.cachedOpts != nil
|
||||
optsChanged := true
|
||||
if c.cachedOpts != nil && reflect.DeepEqual(newOpts, c.cachedOpts) {
|
||||
optsChanged = false
|
||||
}
|
||||
|
||||
if optsChanged {
|
||||
c.cachedOpts = newOpts
|
||||
c.lastText = tsconfigText
|
||||
}
|
||||
c.lastModTime = currentModTime
|
||||
|
||||
c.mu.Unlock()
|
||||
defer c.mu.Lock()
|
||||
|
||||
if hadPreviousOpts && optsChanged {
|
||||
if c.onChange != nil {
|
||||
c.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
return newOpts, tsconfigText, nil
|
||||
}
|
||||
|
||||
func boolToTristate(val bool) tscore.Tristate {
|
||||
if val {
|
||||
return tscore.TSTrue
|
||||
}
|
||||
return tscore.TSFalse
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["es2020"],
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
+230
-150
@@ -1,6 +1,5 @@
|
||||
namespace Frida {
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
public class ControlService : Object {
|
||||
public sealed class ControlService : Object {
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
@@ -12,6 +11,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private HostSession host_session;
|
||||
private HostSessionProvider provider;
|
||||
|
||||
private State state = STOPPED;
|
||||
|
||||
@@ -21,8 +21,12 @@ namespace Frida {
|
||||
|
||||
private Gee.Set<ControlChannel> spawn_gaters = new Gee.HashSet<ControlChannel> ();
|
||||
private Gee.Map<uint, PendingSpawn> pending_spawn = new Gee.HashMap<uint, PendingSpawn> ();
|
||||
private Gee.Map<AgentSessionId?, AgentSessionEntry> sessions =
|
||||
private Gee.Map<AgentSessionId?, AgentSessionEntry> agent_sessions =
|
||||
new Gee.HashMap<AgentSessionId?, AgentSessionEntry> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
private Gee.Map<ChannelId?, ChannelEntry> channels =
|
||||
new Gee.HashMap<ChannelId?, ChannelEntry> (ChannelId.hash, ChannelId.equal);
|
||||
private Gee.Map<ServiceSessionId?, ServiceSessionEntry> service_sessions =
|
||||
new Gee.HashMap<ServiceSessionId?, ServiceSessionEntry> (ServiceSessionId.hash, ServiceSessionId.equal);
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
@@ -35,7 +39,8 @@ namespace Frida {
|
||||
STOPPING
|
||||
}
|
||||
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) {
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) throws Error {
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
|
||||
HostSession session;
|
||||
@@ -63,7 +68,10 @@ namespace Frida {
|
||||
options: opts
|
||||
);
|
||||
|
||||
assign_session (session);
|
||||
assign_session (session, new PrecreatedLocalHostSessionProvider ((LocalHostSession) session));
|
||||
#else
|
||||
throw new Error.NOT_SUPPORTED ("Local backend not available");
|
||||
#endif
|
||||
}
|
||||
|
||||
public async ControlService.with_device (Device device, EndpointParameters endpoint_params,
|
||||
@@ -77,17 +85,7 @@ namespace Frida {
|
||||
options: opts
|
||||
);
|
||||
|
||||
assign_session (session);
|
||||
}
|
||||
|
||||
internal ControlService.with_host_session (HostSession host_session, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null) {
|
||||
Object (
|
||||
endpoint_params: endpoint_params,
|
||||
options: (options != null) ? options : new ControlServiceOptions ()
|
||||
);
|
||||
|
||||
assign_session (host_session);
|
||||
assign_session (session, device.provider);
|
||||
}
|
||||
|
||||
construct {
|
||||
@@ -99,7 +97,7 @@ namespace Frida {
|
||||
main_handler = new ConnectionHandler (this, null);
|
||||
}
|
||||
|
||||
private void assign_session (HostSession session) {
|
||||
private void assign_session (HostSession session, HostSessionProvider provider) {
|
||||
host_session = session;
|
||||
host_session.spawn_added.connect (notify_spawn_added);
|
||||
host_session.child_added.connect (notify_child_added);
|
||||
@@ -107,7 +105,11 @@ namespace Frida {
|
||||
host_session.process_crashed.connect (notify_process_crashed);
|
||||
host_session.output.connect (notify_output);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
host_session.channel_closed.connect (on_channel_closed);
|
||||
host_session.service_session_closed.connect (on_service_session_closed);
|
||||
host_session.uninjected.connect (notify_uninjected);
|
||||
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
@@ -123,7 +125,7 @@ namespace Frida {
|
||||
yield service.start (cancellable);
|
||||
|
||||
if (options.enable_preload) {
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
var base_host_session = host_session as LocalHostSession;
|
||||
if (base_host_session != null)
|
||||
base_host_session.preload.begin (io_cancellable);
|
||||
}
|
||||
@@ -162,9 +164,8 @@ namespace Frida {
|
||||
|
||||
yield main_handler.close (cancellable);
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session != null)
|
||||
yield base_host_session.close (cancellable);
|
||||
if (provider is PrecreatedLocalHostSessionProvider)
|
||||
yield provider.destroy (host_session, cancellable);
|
||||
|
||||
state = STOPPED;
|
||||
}
|
||||
@@ -226,17 +227,15 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private async void teardown_control_channel (ControlChannel channel) {
|
||||
foreach (AgentSessionId id in channel.sessions) {
|
||||
AgentSessionEntry entry = sessions[id];
|
||||
foreach (AgentSessionId id in channel.agent_sessions) {
|
||||
AgentSessionEntry entry = agent_sessions[id];
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session != null)
|
||||
base_host_session.unlink_agent_session (id);
|
||||
provider.unlink_agent_session (host_session, id);
|
||||
|
||||
AgentSession? session = entry.session;
|
||||
|
||||
if (entry.persist_timeout == 0 || session == null) {
|
||||
sessions.unset (id);
|
||||
agent_sessions.unset (id);
|
||||
if (session != null)
|
||||
session.close.begin (io_cancellable);
|
||||
} else {
|
||||
@@ -245,6 +244,24 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ChannelId id in channel.channels.to_array ()) {
|
||||
ChannelEntry entry = channels[id];
|
||||
|
||||
provider.unlink_channel (host_session, id);
|
||||
|
||||
channels.unset (id);
|
||||
|
||||
Channel? ch = entry.channel;
|
||||
if (ch != null)
|
||||
ch.close.begin (null);
|
||||
}
|
||||
|
||||
foreach (ServiceSessionId id in channel.service_sessions.to_array ()) {
|
||||
provider.unlink_service_session (host_session, id);
|
||||
|
||||
service_sessions.unset (id);
|
||||
}
|
||||
|
||||
try {
|
||||
yield disable_spawn_gating (channel);
|
||||
} catch (GLib.Error e) {
|
||||
@@ -379,12 +396,22 @@ namespace Frida {
|
||||
yield parent.reattach (id, requester, cancellable);
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, ControlChannel requester, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
return yield parent.open_channel (address, requester, cancellable);
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, ControlChannel requester, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
return yield parent.open_service (address, requester, cancellable);
|
||||
}
|
||||
|
||||
public void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port, out string token)
|
||||
throws Error {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on Windows");
|
||||
#else
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
var base_host_session = host_session as LocalHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
if (!base_host_session.can_pass_file_descriptors_to_agent_session (id))
|
||||
@@ -454,7 +481,7 @@ namespace Frida {
|
||||
#if !WINDOWS
|
||||
AgentSessionId session_id = transport.session_id;
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
var base_host_session = host_session as LocalHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
|
||||
@@ -537,12 +564,12 @@ namespace Frida {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
requester.sessions.add (id);
|
||||
requester.agent_sessions.add (id);
|
||||
|
||||
var opts = SessionOptions._deserialize (options);
|
||||
|
||||
var entry = new AgentSessionEntry (requester, id, opts.persist_timeout, io_cancellable);
|
||||
sessions[id] = entry;
|
||||
var entry = new AgentSessionEntry (requester, id, opts.persist_timeout);
|
||||
agent_sessions[id] = entry;
|
||||
entry.expired.connect (on_agent_session_expired);
|
||||
|
||||
yield link_session (id, entry, requester, cancellable);
|
||||
@@ -551,11 +578,11 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private async void reattach (AgentSessionId id, ControlChannel requester, Cancellable? cancellable) throws Error, IOError {
|
||||
AgentSessionEntry? entry = sessions[id];
|
||||
AgentSessionEntry? entry = agent_sessions[id];
|
||||
if (entry == null || entry.controller != null)
|
||||
throw new Error.INVALID_OPERATION ("Invalid session ID");
|
||||
|
||||
requester.sessions.add (id);
|
||||
requester.agent_sessions.add (id);
|
||||
|
||||
entry.attach_controller (requester);
|
||||
|
||||
@@ -574,28 +601,7 @@ namespace Frida {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
AgentSession session;
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session != null) {
|
||||
session = yield base_host_session.link_agent_session (id, sink, cancellable);
|
||||
} else {
|
||||
DBusConnection internal_connection = ((DBusProxy) host_session).g_connection;
|
||||
|
||||
try {
|
||||
session = yield internal_connection.get_proxy (null, ObjectPath.for_agent_session (id),
|
||||
DO_NOT_LOAD_PROPERTIES, cancellable);
|
||||
} catch (IOError e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
entry.internal_connection = internal_connection;
|
||||
try {
|
||||
entry.take_internal_registration (
|
||||
internal_connection.register_object (ObjectPath.for_agent_message_sink (id), sink));
|
||||
} catch (IOError e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
var session = yield provider.link_agent_session (host_session, id, sink, cancellable);
|
||||
|
||||
entry.session = session;
|
||||
try {
|
||||
@@ -606,6 +612,52 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private async ChannelId open_channel (string address, ControlChannel requester, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
ChannelId id;
|
||||
try {
|
||||
id = yield host_session.open_channel (address, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
requester.channels.add (id);
|
||||
|
||||
var entry = new ChannelEntry (requester, id);
|
||||
channels[id] = entry;
|
||||
|
||||
var stream = yield provider.link_channel (host_session, id, cancellable);
|
||||
|
||||
Channel channel = new ChannelEndpoint (stream);
|
||||
entry.channel = channel;
|
||||
entry.take_controller_registration (requester.connection.register_object (ObjectPath.for_channel (id), channel));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private async ServiceSessionId open_service (string address, ControlChannel requester, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
ServiceSessionId id;
|
||||
try {
|
||||
id = yield host_session.open_service (address, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
requester.service_sessions.add (id);
|
||||
|
||||
var entry = new ServiceSessionEntry (requester, id);
|
||||
service_sessions[id] = entry;
|
||||
|
||||
var session = yield provider.link_service_session (host_session, id, cancellable);
|
||||
|
||||
entry.session = session;
|
||||
entry.take_controller_registration (
|
||||
requester.connection.register_object (ObjectPath.for_service_session (id), session));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private void notify_spawn_added (HostSpawnInfo info) {
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_added (info);
|
||||
@@ -646,15 +698,33 @@ namespace Frida {
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
AgentSessionEntry entry;
|
||||
if (sessions.unset (id, out entry)) {
|
||||
if (agent_sessions.unset (id, out entry)) {
|
||||
ControlChannel? controller = entry.controller;
|
||||
if (controller != null) {
|
||||
controller.sessions.remove (id);
|
||||
controller.agent_sessions.remove (id);
|
||||
controller.agent_session_detached (id, reason, crash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void on_channel_closed (ChannelId id) {
|
||||
ChannelEntry entry;
|
||||
if (channels.unset (id, out entry)) {
|
||||
ControlChannel controller = entry.controller;
|
||||
controller.channels.remove (id);
|
||||
controller.channel_closed (id);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_service_session_closed (ServiceSessionId id) {
|
||||
ServiceSessionEntry entry;
|
||||
if (service_sessions.unset (id, out entry)) {
|
||||
ControlChannel controller = entry.controller;
|
||||
controller.service_sessions.remove (id);
|
||||
controller.service_session_closed (id);
|
||||
}
|
||||
}
|
||||
|
||||
private void notify_uninjected (InjectorPayloadId id) {
|
||||
all_control_channels ().foreach (channel => {
|
||||
channel.uninjected (id);
|
||||
@@ -663,7 +733,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private void on_agent_session_expired (AgentSessionEntry entry) {
|
||||
sessions.unset (entry.id);
|
||||
agent_sessions.unset (entry.id);
|
||||
}
|
||||
|
||||
private interface Peer : Object {
|
||||
@@ -738,11 +808,21 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public Gee.Set<AgentSessionId?> sessions {
|
||||
public Gee.Set<AgentSessionId?> agent_sessions {
|
||||
get;
|
||||
default = new Gee.HashSet<AgentSessionId?> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
}
|
||||
|
||||
public Gee.Set<ChannelId?> channels {
|
||||
get;
|
||||
default = new Gee.HashSet<ChannelId?> (ChannelId.hash, ChannelId.equal);
|
||||
}
|
||||
|
||||
public Gee.Set<ServiceSessionId?> service_sessions {
|
||||
get;
|
||||
default = new Gee.HashSet<ServiceSessionId?> (ServiceSessionId.hash, ServiceSessionId.equal);
|
||||
}
|
||||
|
||||
private Gee.Set<uint> registrations = new Gee.HashSet<uint> ();
|
||||
private TimeoutSource? ping_timer;
|
||||
|
||||
@@ -867,6 +947,14 @@ namespace Frida {
|
||||
return yield parent.host_session.inject_library_blob (pid, blob, entrypoint, data, cancellable);
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, Cancellable? cancellable) throws GLib.Error {
|
||||
return yield parent.open_channel (address, this, cancellable);
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error {
|
||||
return yield parent.open_service (address, this, cancellable);
|
||||
}
|
||||
|
||||
private async void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port,
|
||||
out string token) throws Error {
|
||||
parent.open_tcp_transport (id, cancellable, out port, out token);
|
||||
@@ -890,14 +978,45 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class AgentSessionEntry {
|
||||
public signal void expired ();
|
||||
|
||||
private abstract class Entry {
|
||||
public ControlChannel? controller {
|
||||
get;
|
||||
private set;
|
||||
get {
|
||||
return _controller;
|
||||
}
|
||||
protected set {
|
||||
unregister_all ();
|
||||
_controller = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ControlChannel? _controller;
|
||||
private Gee.Collection<uint> registrations = new Gee.ArrayList<uint> ();
|
||||
|
||||
protected Entry (ControlChannel? controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
~Entry () {
|
||||
unregister_all ();
|
||||
}
|
||||
|
||||
public void take_controller_registration (uint id) {
|
||||
registrations.add (id);
|
||||
}
|
||||
|
||||
private void unregister_all () {
|
||||
if (_controller == null)
|
||||
return;
|
||||
var connection = _controller.connection;
|
||||
foreach (uint id in registrations)
|
||||
connection.unregister_object (id);
|
||||
registrations.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
private class AgentSessionEntry : Entry {
|
||||
public signal void expired ();
|
||||
|
||||
public AgentSessionId id {
|
||||
get;
|
||||
private set;
|
||||
@@ -913,36 +1032,20 @@ namespace Frida {
|
||||
private set;
|
||||
}
|
||||
|
||||
public DBusConnection? internal_connection {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Cancellable io_cancellable {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Gee.Collection<uint> internal_registrations = new Gee.ArrayList<uint> ();
|
||||
private Gee.Collection<uint> controller_registrations = new Gee.ArrayList<uint> ();
|
||||
|
||||
private TimeoutSource? expiry_timer;
|
||||
|
||||
public AgentSessionEntry (ControlChannel controller, AgentSessionId id, uint persist_timeout,
|
||||
Cancellable io_cancellable) {
|
||||
this.controller = controller;
|
||||
public AgentSessionEntry (ControlChannel? controller, AgentSessionId id, uint persist_timeout) {
|
||||
base (controller);
|
||||
|
||||
this.id = id;
|
||||
this.persist_timeout = persist_timeout;
|
||||
this.io_cancellable = io_cancellable;
|
||||
}
|
||||
|
||||
~AgentSessionEntry () {
|
||||
stop_expiry_timer ();
|
||||
unregister_all ();
|
||||
}
|
||||
|
||||
public void detach_controller () {
|
||||
unregister_all ();
|
||||
controller = null;
|
||||
session = null;
|
||||
|
||||
@@ -956,27 +1059,6 @@ namespace Frida {
|
||||
controller = c;
|
||||
}
|
||||
|
||||
public void take_internal_registration (uint id) {
|
||||
internal_registrations.add (id);
|
||||
}
|
||||
|
||||
public void take_controller_registration (uint id) {
|
||||
controller_registrations.add (id);
|
||||
}
|
||||
|
||||
private void unregister_all () {
|
||||
if (controller != null)
|
||||
unregister_all_in (controller_registrations, controller.connection);
|
||||
if (internal_connection != null)
|
||||
unregister_all_in (internal_registrations, internal_connection);
|
||||
}
|
||||
|
||||
private void unregister_all_in (Gee.Collection<uint> ids, DBusConnection connection) {
|
||||
foreach (uint id in ids)
|
||||
connection.unregister_object (id);
|
||||
ids.clear ();
|
||||
}
|
||||
|
||||
private void start_expiry_timer () {
|
||||
if (expiry_timer != null)
|
||||
return;
|
||||
@@ -995,57 +1077,55 @@ namespace Frida {
|
||||
expiry_timer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class ControlService : Object {
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) {
|
||||
}
|
||||
|
||||
public ControlService.with_host_session (HostSession host_session, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null) {
|
||||
}
|
||||
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Local backend not available");
|
||||
}
|
||||
|
||||
public void start_sync (Cancellable? cancellable = null) throws Error, IOError {
|
||||
create<StartTask> ().execute (cancellable);
|
||||
}
|
||||
|
||||
private class StartTask : ControlServiceTask<void> {
|
||||
protected override async void perform_operation () throws Error, IOError {
|
||||
yield parent.start (cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable = null) throws Error, IOError {
|
||||
}
|
||||
|
||||
public void stop_sync (Cancellable? cancellable = null) throws Error, IOError {
|
||||
create<StopTask> ().execute (cancellable);
|
||||
}
|
||||
|
||||
private class StopTask : ControlServiceTask<void> {
|
||||
protected override async void perform_operation () throws Error, IOError {
|
||||
yield parent.stop (cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private T create<T> () {
|
||||
return Object.new (typeof (T), parent: this);
|
||||
}
|
||||
|
||||
private abstract class ControlServiceTask<T> : AsyncTask<T> {
|
||||
public weak ControlService parent {
|
||||
private class ChannelEntry : Entry {
|
||||
public ChannelId id {
|
||||
get;
|
||||
construct;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Channel? channel {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ChannelEntry (ControlChannel controller, ChannelId id) {
|
||||
base (controller);
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private class ServiceSessionEntry : Entry {
|
||||
public ServiceSessionId id {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ServiceSession? session {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ServiceSessionEntry (ControlChannel controller, ServiceSessionId id) {
|
||||
base (controller);
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public class ControlServiceOptions : Object {
|
||||
private sealed class PrecreatedLocalHostSessionProvider : LocalHostSessionProvider {
|
||||
public PrecreatedLocalHostSessionProvider (LocalHostSession session) {
|
||||
take_host_session (session);
|
||||
}
|
||||
|
||||
protected override LocalHostSession make_host_session (HostSessionOptions? options) throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ControlServiceOptions : Object {
|
||||
public string? sysroot {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -21,7 +21,7 @@ const substratePidsPending = new Map();
|
||||
rpc.exports = {
|
||||
dispose() {
|
||||
if (suspendedPids.size > 0) {
|
||||
const kill = new NativeFunction(Module.getExportByName(null, 'kill'), 'int', ['int', 'int']);
|
||||
const kill = new NativeFunction(Module.getGlobalExportByName('kill'), 'int', ['int', 'int']);
|
||||
for (const pid of suspendedPids) {
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ rpc.exports = {
|
||||
|
||||
applyJailbreakQuirks();
|
||||
|
||||
Interceptor.attach(Module.getExportByName('/usr/lib/system/libsystem_kernel.dylib', '__posix_spawn'), {
|
||||
Interceptor.attach(Process.getModuleByName('/usr/lib/system/libsystem_kernel.dylib').getExportByName('__posix_spawn'), {
|
||||
onEnter(args) {
|
||||
const env = parseStringv(args[4]);
|
||||
const prewarm = isPrewarmLaunch(env);
|
||||
@@ -238,7 +238,7 @@ function instrumentInserter(at) {
|
||||
}
|
||||
|
||||
function findJbdCallImpl() {
|
||||
const impl = Module.findExportByName(null, 'jbd_call');
|
||||
const impl = Module.findGlobalExportByName('jbd_call');
|
||||
if (impl !== null)
|
||||
return impl;
|
||||
|
||||
@@ -257,7 +257,7 @@ function findSubstrateLauncher() {
|
||||
if (Process.arch !== 'arm64')
|
||||
return null;
|
||||
|
||||
const imp = Module.enumerateImports('/sbin/launchd').filter(imp => imp.name === 'posix_spawn')[0];
|
||||
const imp = Process.mainModule.enumerateImports().find(imp => imp.name === 'posix_spawn');
|
||||
if (imp === undefined)
|
||||
return null;
|
||||
const impl = imp.slot.readPointer().strip();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ObjC from 'frida-objc-bridge';
|
||||
|
||||
const YES = ptr(1);
|
||||
|
||||
const {
|
||||
@@ -53,7 +55,8 @@ function tryInitialize() {
|
||||
}
|
||||
|
||||
if (!tryInitialize()) {
|
||||
const listener = Interceptor.attach(Module.getExportByName('/usr/lib/system/libsystem_trace.dylib', 'os_log_type_enabled'), () => {
|
||||
const listener = Interceptor.attach(
|
||||
Process.getModuleByName('/usr/lib/system/libsystem_trace.dylib').getExportByName('os_log_type_enabled'), () => {
|
||||
tryInitialize();
|
||||
listener.detach();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
osanalytics_js = custom_target('osanalytics.js',
|
||||
input: [
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'index.js',
|
||||
],
|
||||
output: [
|
||||
'osanalytics.js',
|
||||
],
|
||||
command: [
|
||||
python,
|
||||
compile_js_agent,
|
||||
'@OUTPUT@',
|
||||
'@PRIVATE_DIR@',
|
||||
npm,
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
{
|
||||
"name": "frida-osanalytics-agent",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frida-osanalytics-agent",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"frida-objc-bridge": "^8.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.5",
|
||||
"@esbuild/android-arm": "0.25.5",
|
||||
"@esbuild/android-arm64": "0.25.5",
|
||||
"@esbuild/android-x64": "0.25.5",
|
||||
"@esbuild/darwin-arm64": "0.25.5",
|
||||
"@esbuild/darwin-x64": "0.25.5",
|
||||
"@esbuild/freebsd-arm64": "0.25.5",
|
||||
"@esbuild/freebsd-x64": "0.25.5",
|
||||
"@esbuild/linux-arm": "0.25.5",
|
||||
"@esbuild/linux-arm64": "0.25.5",
|
||||
"@esbuild/linux-ia32": "0.25.5",
|
||||
"@esbuild/linux-loong64": "0.25.5",
|
||||
"@esbuild/linux-mips64el": "0.25.5",
|
||||
"@esbuild/linux-ppc64": "0.25.5",
|
||||
"@esbuild/linux-riscv64": "0.25.5",
|
||||
"@esbuild/linux-s390x": "0.25.5",
|
||||
"@esbuild/linux-x64": "0.25.5",
|
||||
"@esbuild/netbsd-arm64": "0.25.5",
|
||||
"@esbuild/netbsd-x64": "0.25.5",
|
||||
"@esbuild/openbsd-arm64": "0.25.5",
|
||||
"@esbuild/openbsd-x64": "0.25.5",
|
||||
"@esbuild/sunos-x64": "0.25.5",
|
||||
"@esbuild/win32-arm64": "0.25.5",
|
||||
"@esbuild/win32-ia32": "0.25.5",
|
||||
"@esbuild/win32-x64": "0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-objc-bridge": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/frida-objc-bridge/-/frida-objc-bridge-8.0.5.tgz",
|
||||
"integrity": "sha512-0xnL0VgxSQXgLj3S33S0kdAvLrCHSCIZb9HVraEgEyKnf9wOcu0KZ9fI5xzx0e0y+ry/g2Vl3yw9j3dsn92Ffg==",
|
||||
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "frida-osanalytics-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent used by Frida's Darwin backend on iOS/iPadOS/tvOS",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild index.js --outfile=osanalytics.js --bundle --platform=node --target=es2022 --minify"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"frida-objc-bridge": "^8.0.5"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
const LIBSYSTEM_KERNEL_PATH = '/usr/lib/system/libsystem_kernel.dylib';
|
||||
const CORESYMBOLICATION_PATH = '/System/Library/PrivateFrameworks/CoreSymbolication.framework/CoreSymbolication';
|
||||
const CRASH_REPORTER_SUPPORT_PATH = '/System/Library/PrivateFrameworks/CrashReporterSupport.framework/CrashReporterSupport';
|
||||
import ObjC from 'frida-objc-bridge';
|
||||
|
||||
const YES = ptr(1);
|
||||
|
||||
const CSTypeRef = ['pointer', 'pointer'];
|
||||
@@ -17,42 +16,45 @@ const complexFuncOptions = {
|
||||
scheduling: 'cooperative',
|
||||
exceptions: 'propagate'
|
||||
};
|
||||
const libsystemKernel = Process.getModuleByName('/usr/lib/system/libsystem_kernel.dylib');
|
||||
const _pidForTask = new NativeFunction(
|
||||
Module.getExportByName(LIBSYSTEM_KERNEL_PATH, 'pid_for_task'),
|
||||
libsystemKernel.getExportByName('pid_for_task'),
|
||||
'int',
|
||||
['uint', 'pointer'],
|
||||
simpleFuncOptions
|
||||
);
|
||||
const unlink = new NativeFunction(
|
||||
Module.getExportByName(LIBSYSTEM_KERNEL_PATH, 'unlink'),
|
||||
libsystemKernel.getExportByName('unlink'),
|
||||
'int',
|
||||
['pointer'],
|
||||
simpleFuncOptions
|
||||
);
|
||||
const coreSymbolication = Process.getModuleByName('/System/Library/PrivateFrameworks/CoreSymbolication.framework/CoreSymbolication');
|
||||
const CSSymbolicatorGetSymbolWithAddressAtTime = new NativeFunction(
|
||||
Module.getExportByName(CORESYMBOLICATION_PATH, 'CSSymbolicatorGetSymbolWithAddressAtTime'),
|
||||
coreSymbolication.getExportByName('CSSymbolicatorGetSymbolWithAddressAtTime'),
|
||||
CSTypeRef,
|
||||
[CSTypeRef, 'uint64', 'uint64'],
|
||||
complexFuncOptions
|
||||
);
|
||||
const CSIsNull = new NativeFunction(
|
||||
Module.getExportByName(CORESYMBOLICATION_PATH, 'CSIsNull'),
|
||||
coreSymbolication.getExportByName('CSIsNull'),
|
||||
'int',
|
||||
[CSTypeRef],
|
||||
simpleFuncOptions
|
||||
);
|
||||
const mappedMemoryRead = new NativeFunction(
|
||||
Module.getExportByName(CORESYMBOLICATION_PATH, 'mapped_memory_read'),
|
||||
coreSymbolication.getExportByName('mapped_memory_read'),
|
||||
'uint',
|
||||
['pointer', 'uint64', 'uint64', 'pointer'],
|
||||
simpleFuncOptions
|
||||
);
|
||||
const mappedMemoryReadPointer = new NativeFunction(
|
||||
Module.getExportByName(CORESYMBOLICATION_PATH, 'mapped_memory_read_pointer'),
|
||||
coreSymbolication.getExportByName('mapped_memory_read_pointer'),
|
||||
'uint',
|
||||
['pointer', 'uint64', 'pointer'],
|
||||
simpleFuncOptions
|
||||
);
|
||||
const crashReporterSupport = Process.getModuleByName('/System/Library/PrivateFrameworks/CrashReporterSupport.framework/CrashReporterSupport');
|
||||
|
||||
const {
|
||||
AppleErrorReport,
|
||||
@@ -67,7 +69,7 @@ const sessions = new Map();
|
||||
let osaHookState = 'pending';
|
||||
|
||||
function initialize() {
|
||||
const listener = Interceptor.attach(Module.getExportByName(CRASH_REPORTER_SUPPORT_PATH, 'CRCreateDirectoryStructure'), () => {
|
||||
const listener = Interceptor.attach(crashReporterSupport.getExportByName('CRCreateDirectoryStructure'), () => {
|
||||
applyInstrumentation();
|
||||
listener.detach();
|
||||
});
|
||||
@@ -144,7 +146,7 @@ function applyInstrumentation() {
|
||||
},
|
||||
});
|
||||
|
||||
Interceptor.attach(Module.getExportByName(CORESYMBOLICATION_PATH, 'task_is_64bit'), {
|
||||
Interceptor.attach(coreSymbolication.getExportByName('task_is_64bit'), {
|
||||
onEnter(args) {
|
||||
this.pid = pidForTask(args[0].toUInt32());
|
||||
},
|
||||
@@ -216,7 +218,7 @@ function applyInstrumentation() {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Interceptor.attach(Module.getExportByName(LIBSYSTEM_KERNEL_PATH, 'rename'), {
|
||||
Interceptor.attach(libsystemKernel.getExportByName('rename'), {
|
||||
onEnter(args) {
|
||||
const newPath = args[1].readUtf8String();
|
||||
const session = getSession(this.threadId, 'rename');
|
||||
@@ -225,7 +227,7 @@ function applyInstrumentation() {
|
||||
},
|
||||
});
|
||||
|
||||
Interceptor.attach(Module.getExportByName(LIBSYSTEM_KERNEL_PATH, 'open_dprotected_np'), {
|
||||
Interceptor.attach(libsystemKernel.getExportByName('open_dprotected_np'), {
|
||||
onEnter(args) {
|
||||
const path = args[0].readUtf8String();
|
||||
this.isCrashLog = /\.ips$/.test(path);
|
||||
@@ -237,7 +239,7 @@ function applyInstrumentation() {
|
||||
},
|
||||
});
|
||||
|
||||
Interceptor.attach(Module.getExportByName(LIBSYSTEM_KERNEL_PATH, 'write'), {
|
||||
Interceptor.attach(libsystemKernel.getExportByName('write'), {
|
||||
onEnter(args) {
|
||||
const fd = args[0].toInt32();
|
||||
this.buf = args[1];
|
||||
@@ -266,7 +268,7 @@ function applyInstrumentation() {
|
||||
});
|
||||
}
|
||||
|
||||
Interceptor.attach(Module.getExportByName(CRASH_REPORTER_SUPPORT_PATH, 'OSAPreferencesGetBoolValue'), {
|
||||
Interceptor.attach(crashReporterSupport.getExportByName('OSAPreferencesGetBoolValue'), {
|
||||
onEnter(args) {
|
||||
this.name = new ObjC.Object(args[0]).toString();
|
||||
this.domain = new ObjC.Object(args[1]).toString();
|
||||
@@ -0,0 +1,18 @@
|
||||
reportcrash_js = custom_target('reportcrash.js',
|
||||
input: [
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'index.js',
|
||||
],
|
||||
output: [
|
||||
'reportcrash.js',
|
||||
],
|
||||
command: [
|
||||
python,
|
||||
compile_js_agent,
|
||||
'@OUTPUT@',
|
||||
'@PRIVATE_DIR@',
|
||||
npm,
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
{
|
||||
"name": "frida-reportcrash-agent",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frida-reportcrash-agent",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"frida-objc-bridge": "^8.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.5",
|
||||
"@esbuild/android-arm": "0.25.5",
|
||||
"@esbuild/android-arm64": "0.25.5",
|
||||
"@esbuild/android-x64": "0.25.5",
|
||||
"@esbuild/darwin-arm64": "0.25.5",
|
||||
"@esbuild/darwin-x64": "0.25.5",
|
||||
"@esbuild/freebsd-arm64": "0.25.5",
|
||||
"@esbuild/freebsd-x64": "0.25.5",
|
||||
"@esbuild/linux-arm": "0.25.5",
|
||||
"@esbuild/linux-arm64": "0.25.5",
|
||||
"@esbuild/linux-ia32": "0.25.5",
|
||||
"@esbuild/linux-loong64": "0.25.5",
|
||||
"@esbuild/linux-mips64el": "0.25.5",
|
||||
"@esbuild/linux-ppc64": "0.25.5",
|
||||
"@esbuild/linux-riscv64": "0.25.5",
|
||||
"@esbuild/linux-s390x": "0.25.5",
|
||||
"@esbuild/linux-x64": "0.25.5",
|
||||
"@esbuild/netbsd-arm64": "0.25.5",
|
||||
"@esbuild/netbsd-x64": "0.25.5",
|
||||
"@esbuild/openbsd-arm64": "0.25.5",
|
||||
"@esbuild/openbsd-x64": "0.25.5",
|
||||
"@esbuild/sunos-x64": "0.25.5",
|
||||
"@esbuild/win32-arm64": "0.25.5",
|
||||
"@esbuild/win32-ia32": "0.25.5",
|
||||
"@esbuild/win32-x64": "0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-objc-bridge": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/frida-objc-bridge/-/frida-objc-bridge-8.0.5.tgz",
|
||||
"integrity": "sha512-0xnL0VgxSQXgLj3S33S0kdAvLrCHSCIZb9HVraEgEyKnf9wOcu0KZ9fI5xzx0e0y+ry/g2Vl3yw9j3dsn92Ffg==",
|
||||
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "frida-reportcrash-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent used by Frida's Darwin backend on iOS/iPadOS/tvOS",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild index.js --outfile=reportcrash.js --bundle --platform=node --target=es2022 --minify"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"frida-objc-bridge": "^8.0.5"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ const POSIX_SPAWN_START_SUSPENDED = 0x0080;
|
||||
|
||||
applyJailbreakQuirks();
|
||||
|
||||
Interceptor.attach(Module.getExportByName('/usr/lib/system/libsystem_kernel.dylib', '__posix_spawn'), {
|
||||
Interceptor.attach(Process.getModuleByName('/usr/lib/system/libsystem_kernel.dylib').getExportByName('__posix_spawn'), {
|
||||
onEnter(args) {
|
||||
const attrs = args[2].add(Process.pointerSize).readPointer();
|
||||
|
||||
@@ -42,7 +42,7 @@ function sabotageJbdCall(jbdCallImpl) {
|
||||
}
|
||||
|
||||
function instrumentSubstrateBootstrapper(bootstrapper) {
|
||||
Interceptor.attach(Module.getExportByName('/usr/lib/system/libdyld.dylib', 'dlopen'), {
|
||||
Interceptor.attach(Process.getModuleByName('/usr/lib/system/libdyld.dylib').getExportByName('dlopen'), {
|
||||
onEnter(args) {
|
||||
this.path = args[0].readUtf8String();
|
||||
},
|
||||
@@ -66,7 +66,7 @@ function instrumentSubstrateExec(exec) {
|
||||
}
|
||||
|
||||
function findJbdCallImpl() {
|
||||
const impl = Module.findExportByName(null, 'jbd_call');
|
||||
const impl = Module.findGlobalExportByName('jbd_call');
|
||||
if (impl !== null)
|
||||
return impl;
|
||||
|
||||
|
||||
@@ -1,98 +1,24 @@
|
||||
namespace Frida {
|
||||
public class DarwinHostSessionBackend : Object, HostSessionBackend {
|
||||
private DarwinHostSessionProvider local_provider;
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
assert (local_provider == null);
|
||||
local_provider = new DarwinHostSessionProvider ();
|
||||
provider_available (local_provider);
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable) throws IOError {
|
||||
assert (local_provider != null);
|
||||
provider_unavailable (local_provider);
|
||||
yield local_provider.close (cancellable);
|
||||
local_provider = null;
|
||||
public sealed class DarwinHostSessionBackend : LocalHostSessionBackend {
|
||||
protected override LocalHostSessionProvider make_provider () {
|
||||
return new DarwinHostSessionProvider ();
|
||||
}
|
||||
}
|
||||
|
||||
public class DarwinHostSessionProvider : Object, HostSessionProvider {
|
||||
public string id {
|
||||
get { return "local"; }
|
||||
}
|
||||
|
||||
public string name {
|
||||
get { return "Local System"; }
|
||||
}
|
||||
|
||||
public Variant? icon {
|
||||
get { return _icon; }
|
||||
}
|
||||
private Variant? _icon;
|
||||
|
||||
public HostSessionProviderKind kind {
|
||||
get { return HostSessionProviderKind.LOCAL; }
|
||||
}
|
||||
|
||||
private DarwinHostSession host_session;
|
||||
|
||||
construct {
|
||||
_icon = _try_extract_icon ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (host_session == null)
|
||||
return;
|
||||
host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
yield host_session.close (cancellable);
|
||||
host_session = null;
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
public sealed class DarwinHostSessionProvider : LocalHostSessionProvider {
|
||||
protected override LocalHostSession make_host_session (HostSessionOptions? options) throws Error {
|
||||
var tempdir = new TemporaryDirectory ();
|
||||
|
||||
host_session = new DarwinHostSession (new DarwinHelperProcess (tempdir), tempdir);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
return new DarwinHostSession (new DarwinHelperProcess (tempdir), tempdir);
|
||||
}
|
||||
|
||||
public async void destroy (HostSession session, Cancellable? cancellable) throws Error, IOError {
|
||||
if (session != host_session)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid host session");
|
||||
|
||||
host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
|
||||
yield host_session.close (cancellable);
|
||||
host_session = null;
|
||||
}
|
||||
|
||||
public async AgentSession link_agent_session (HostSession host_session, AgentSessionId id, AgentMessageSink sink,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
if (host_session != this.host_session)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid host session");
|
||||
|
||||
return yield this.host_session.link_agent_session (id, sink, cancellable);
|
||||
}
|
||||
|
||||
public void unlink_agent_session (HostSession host_session, AgentSessionId id) {
|
||||
if (host_session != this.host_session)
|
||||
return;
|
||||
|
||||
this.host_session.unlink_agent_session (id);
|
||||
}
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
agent_session_detached (id, reason, crash);
|
||||
protected override Variant? load_icon () {
|
||||
return _try_extract_icon ();
|
||||
}
|
||||
|
||||
public extern static Variant? _try_extract_icon ();
|
||||
}
|
||||
|
||||
public class DarwinHostSession : BaseDBusHostSession {
|
||||
public sealed class DarwinHostSession : LocalHostSession {
|
||||
public DarwinHelper helper {
|
||||
get;
|
||||
construct;
|
||||
@@ -410,7 +336,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
#if IOS || TVOS
|
||||
private class FruitController : Object, MappedAgentContainer {
|
||||
private sealed class FruitController : Object, MappedAgentContainer {
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void process_crashed (CrashInfo crash);
|
||||
@@ -815,7 +741,7 @@ namespace Frida {
|
||||
crash_deliveries.unset (delivery.pid);
|
||||
}
|
||||
|
||||
private class CrashDelivery : Object {
|
||||
private sealed class CrashDelivery : Object {
|
||||
public signal void expired ();
|
||||
|
||||
public uint pid {
|
||||
@@ -882,7 +808,7 @@ namespace Frida {
|
||||
|
||||
private delegate void FoundMappedAgentFunc (MappedAgent agent);
|
||||
|
||||
private class MappedAgent {
|
||||
private sealed class MappedAgent {
|
||||
public uint id {
|
||||
get;
|
||||
private set;
|
||||
@@ -905,7 +831,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class LaunchdAgent : InternalAgent {
|
||||
private sealed class LaunchdAgent : InternalAgent {
|
||||
public signal void app_launch_started (string identifier, uint pid);
|
||||
public signal void app_launch_completed (string identifier, uint pid, GLib.Error? error);
|
||||
public signal void spawn_preparation_started (HostSpawnInfo info);
|
||||
@@ -1015,7 +941,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class XpcProxyAgent : InternalAgent {
|
||||
private sealed class XpcProxyAgent : InternalAgent {
|
||||
public string identifier {
|
||||
get;
|
||||
construct;
|
||||
@@ -1057,7 +983,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class ReportCrashAgent : InternalAgent {
|
||||
private sealed class ReportCrashAgent : InternalAgent {
|
||||
public signal void crash_detected (uint pid);
|
||||
public signal void crash_received (CrashInfo crash);
|
||||
|
||||
@@ -1289,7 +1215,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class OSAnalyticsAgent : InternalAgent {
|
||||
private sealed class OSAnalyticsAgent : InternalAgent {
|
||||
public uint pid {
|
||||
get;
|
||||
construct;
|
||||
|
||||
@@ -2027,7 +2027,9 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
}
|
||||
}
|
||||
|
||||
modern_entry_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld44APIs19_libdyld_initializeEPKNS_16LibSystemHelpersE");
|
||||
modern_entry_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld44APIs19_libdyld_initializeEv");
|
||||
if (modern_entry_address == 0)
|
||||
modern_entry_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld44APIs19_libdyld_initializeEPKNS_16LibSystemHelpersE");
|
||||
instance->dyld_flavor = (modern_entry_address != 0) ? FRIDA_DYLD_V4_PLUS : FRIDA_DYLD_V3_MINUS;
|
||||
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
@@ -2113,7 +2115,12 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
GumAddress restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
"__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPK15DyldSharedCachePv");
|
||||
"__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN6mach_o6HeaderEPK15DyldSharedCachePv");
|
||||
if (restart_with_dyld_in_cache == 0)
|
||||
{
|
||||
restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
"__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPK15DyldSharedCachePv");
|
||||
}
|
||||
if (restart_with_dyld_in_cache == 0)
|
||||
{
|
||||
restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
|
||||
Loaded 100 of 246 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user