2021-01-29 10:22:28 +13:00
|
|
|
#!/bin/python3
|
|
|
|
#
|
|
|
|
# vGPU unlock script for consumer GPUs.
|
|
|
|
#
|
|
|
|
# Copyright 2021 Jonathan Johansson
|
|
|
|
# This file is part of the "vgpu_unlock" project, and is distributed under the MIT License.
|
|
|
|
# See the LICENSE file for more details.
|
|
|
|
#
|
2021-04-12 10:10:28 +12:00
|
|
|
# Contributions from Krutav Shah and the vGPU Unlocking community included :)
|
|
|
|
#
|
2021-01-29 10:22:28 +13:00
|
|
|
|
|
|
|
import errno
|
|
|
|
import frida
|
|
|
|
import os
|
|
|
|
import queue
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
|
|
|
|
script_source = r"""
|
2021-04-18 03:37:07 +12:00
|
|
|
var syslog_func = new NativeFunction(Module.getExportByName(null, "syslog"),
|
|
|
|
"void",
|
|
|
|
["int", "pointer", "...", "pointer"]);
|
|
|
|
|
|
|
|
var syslog = function(message) {
|
|
|
|
var format_ptr = Memory.allocUtf8String("%s");
|
|
|
|
var message_ptr = Memory.allocUtf8String(message);
|
|
|
|
syslog_func(5, format_ptr, message_ptr);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2021-01-29 10:22:28 +13:00
|
|
|
// Value of the "request" argument used by nvidia-vgpud and nvidia-vgpu-mgr
|
|
|
|
// when calling ioctl to read the PCI device ID and type (and possibly
|
|
|
|
// other things) from the GPU.
|
|
|
|
var REQ_QUERY_GPU = ptr("0xC020462A");
|
|
|
|
|
|
|
|
// When issuing ioctl with REQ_QUERY_GPU then the "argp" argument is a
|
|
|
|
// pointer to a structure something like this:
|
|
|
|
//
|
|
|
|
// struct arg {
|
|
|
|
// uint32_t unknown_1; // Initialized prior to call.
|
|
|
|
// uint32_t unknown_2; // Initialized prior to call.
|
|
|
|
// uint32_t op_type; // Operation type, see comment below.
|
|
|
|
// uint32_t padding_1; // Always set to 0 prior to call.
|
|
|
|
// void* result; // Pointer initialized prior to call.
|
|
|
|
// // Pointee initialized to 0 prior to call.
|
|
|
|
// // Pointee is written by ioctl call.
|
|
|
|
// uint32_t unknown_4; // Set to 0x10 for READ_PCI_ID and set to 4 for
|
|
|
|
// READ_DEV_TYPE prior to call.
|
|
|
|
// uint32_t status; // Written by ioctl call. See comment below.
|
|
|
|
// }
|
|
|
|
|
|
|
|
// These are the observed values for the op_type member.
|
|
|
|
var OP_READ_DEV_TYPE = 0x800289; // *result type is uint64_t.
|
2021-02-28 05:15:17 +13:00
|
|
|
var OP_READ_PCI_ID = 0x20801801; // *result type is uint16_t[4], the second
|
|
|
|
// element (index 1) is the device ID, the
|
|
|
|
// forth element (index 3) is the subsystem
|
|
|
|
// ID.
|
2021-01-29 10:22:28 +13:00
|
|
|
|
|
|
|
// nvidia-vgpu-mgr expects this value for a vGPU capable GPU.
|
|
|
|
var DEV_TYPE_VGPU_CAPABLE = uint64(3);
|
|
|
|
|
|
|
|
// When ioctl returns success (retval >= 0) but sets the status value of
|
|
|
|
// the arg structure to 3 then nvidia-vgpud will sleep for a bit (first
|
|
|
|
// 0.1s then 1s then 10s) then issue the same ioctl call again until the
|
|
|
|
// status differs from 3. It will attempt this for up to 24h before giving
|
|
|
|
// up.
|
2021-04-18 03:37:07 +12:00
|
|
|
var STATUS_OK = 0;
|
2021-01-29 10:22:28 +13:00
|
|
|
var STATUS_TRY_AGAIN = 3;
|
|
|
|
|
|
|
|
Interceptor.attach(Module.getExportByName(null, "ioctl"), {
|
|
|
|
onEnter(args) {
|
|
|
|
this.request = args[1];
|
|
|
|
this.argp = args[2];
|
|
|
|
},
|
|
|
|
onLeave(retVal) {
|
|
|
|
if(!this.request.equals(REQ_QUERY_GPU)) {
|
|
|
|
// Not a call we care about.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(retVal.toInt32() < 0) {
|
|
|
|
// Call failed.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup status value according to struct above.
|
|
|
|
var status = this.argp.add(0x1C).readU32();
|
|
|
|
|
|
|
|
if(status == STATUS_TRY_AGAIN) {
|
|
|
|
// Driver will try again.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var op_type = this.argp.add(8).readU32();
|
|
|
|
|
|
|
|
if(op_type == OP_READ_PCI_ID) {
|
2021-02-28 05:15:17 +13:00
|
|
|
// Lookup address of the device and subsystem IDs.
|
2021-01-29 10:22:28 +13:00
|
|
|
var devid_ptr = this.argp.add(0x10).readPointer().add(2);
|
2021-02-28 05:15:17 +13:00
|
|
|
var subsysid_ptr = this.argp.add(0x10).readPointer().add(6);
|
2021-01-29 10:22:28 +13:00
|
|
|
|
|
|
|
// Now we replace the device ID with a spoofed value that needs to
|
|
|
|
// be determined such that the spoofed value represents a GPU with
|
|
|
|
// vGPU support that uses the same GPU chip as our actual GPU.
|
|
|
|
var actual_devid = devid_ptr.readU16();
|
|
|
|
var spoofed_devid = actual_devid;
|
2021-02-28 05:15:17 +13:00
|
|
|
var actual_subsysid = subsysid_ptr.readU16();
|
|
|
|
var spoofed_subsysid = actual_subsysid;
|
2021-01-29 10:22:28 +13:00
|
|
|
|
2021-04-12 10:10:28 +12:00
|
|
|
|
|
|
|
// GM107
|
2021-04-14 06:04:16 +12:00
|
|
|
if(actual_devid == 0x1390 || // GTX 845M
|
|
|
|
actual_devid == 0x1391 || // GTX 850M
|
|
|
|
actual_devid == 0x1392 || // GTX 860M
|
|
|
|
actual_devid == 0x139a || // GTX 950M
|
|
|
|
actual_devid == 0x139b || // GTX 960M
|
|
|
|
actual_devid == 0x139c || // GTX 940M
|
|
|
|
actual_devid == 0x139d || // GTX 750 Ti Maxwell
|
|
|
|
actual_devid == 0x179c || // GTX 940MX
|
|
|
|
actual_devid == 0x1380 || // GTX 750 Ti Maxwell
|
|
|
|
actual_devid == 0x1381 || // GTX 750 Maxwell
|
|
|
|
actual_devid == 0x1382 || // GTX 745 Maxwell
|
|
|
|
actual_devid == 0x13b0 || // Quadro M2000 Mobile
|
|
|
|
actual_devid == 0x13b1 || // Quadro M1000 Mobile
|
|
|
|
actual_devid == 0x13b2 || // Quadro M600 Mobile
|
|
|
|
actual_devid == 0x13b3 || // Quadro K2200 Mobile
|
|
|
|
actual_devid == 0x13b4 || // Quadro M620 Mobile
|
|
|
|
actual_devid == 0x13b6 || // Quadro M1200 Mobile
|
|
|
|
actual_devid == 0x13b9 || // NVS 810
|
|
|
|
actual_devid == 0x13ba || // Quadro K2200
|
|
|
|
actual_devid == 0x13bb || // Quadro K620
|
|
|
|
actual_devid == 0x13bc) { // Quadro K1200
|
2021-04-12 10:10:28 +12:00
|
|
|
spoofed_devid = 0x13bd; // Tesla M10
|
|
|
|
}
|
|
|
|
|
|
|
|
// GM204
|
2021-04-14 06:04:16 +12:00
|
|
|
if(actual_devid == 0x13c3 || // GTX 960 GM204 OEM Edition
|
2021-04-12 10:10:28 +12:00
|
|
|
actual_devid == 0x13d9 || // GTX 965M
|
|
|
|
actual_devid == 0x13d8 || // GTX 970M
|
|
|
|
actual_devid == 0x13c2 || // GTX 970
|
|
|
|
actual_devid == 0x13d7 || // GTX 980M
|
|
|
|
actual_devid == 0x13c0 || // GTX 980
|
2021-04-14 06:04:16 +12:00
|
|
|
actual_devid == 0x13c1 || // GM204 Unknown
|
2021-04-12 10:10:28 +12:00
|
|
|
actual_devid == 0x13f1 || // Quadro M4000
|
|
|
|
actual_devid == 0x13f0) { // Quadro M5000
|
|
|
|
spoofed_devid = 0x13f2; // Tesla M60
|
|
|
|
}
|
|
|
|
|
2021-01-29 10:22:28 +13:00
|
|
|
// GP102
|
|
|
|
if(actual_devid == 0x1b00 || // TITAN X (Pascal)
|
|
|
|
actual_devid == 0x1b02 || // TITAN Xp
|
|
|
|
actual_devid == 0x1b06 || // GTX 1080 Ti
|
|
|
|
actual_devid == 0x1b30) { // Quadro P6000
|
|
|
|
spoofed_devid = 0x1b38; // Tesla P40
|
|
|
|
}
|
2021-04-14 06:04:16 +12:00
|
|
|
|
|
|
|
// GP108 (Merged with Tesla P4, will move to M10 in future due to 2GB VRAM)
|
|
|
|
if(actual_devid == 0x1d01 || // GT 1030
|
|
|
|
actual_devid == 0x1d10 || // MX150 Mobile
|
|
|
|
actual_devid == 0x1d11 || // MX230 Mobile
|
|
|
|
actual_devid == 0x1d12 || // MX150 Mobile
|
|
|
|
actual_devid == 0x1d13 || // MX250 Mobile
|
|
|
|
actual_devid == 0x1d16 || // MX330 Mobile
|
|
|
|
|
|
|
|
// GP107 (Merged with Tesla P4, may move to M10 in future due to low VRAM)
|
|
|
|
actual_devid == 0x1cb1 || // Quadro P1000
|
2021-04-19 07:23:09 +12:00
|
|
|
actual_devid == 0x1cb3 || // Quadro P400
|
2021-04-14 06:04:16 +12:00
|
|
|
actual_devid == 0x1c81 || // GTX 1050 2GB
|
|
|
|
actual_devid == 0x1c82 || // GTX 1050 Ti
|
|
|
|
actual_devid == 0x1c83 || // GTX 1050 3GB
|
|
|
|
actual_devid == 0x1c8c || // GTX 1050 Ti Mobile
|
|
|
|
actual_devid == 0x1c8d || // GTX 1050 Mobile
|
|
|
|
actual_devid == 0x1c8f || // GTX 1050 Ti Max-Q
|
|
|
|
actual_devid == 0x1c90 || // MX150 Mobile
|
|
|
|
actual_devid == 0x1c92 || // GTX 1050 Mobile
|
|
|
|
actual_devid == 0x1c94 || // MX350 Mobile
|
|
|
|
actual_devid == 0x1c96 || // MX350 Mobile
|
|
|
|
|
|
|
|
// GP106 (Merged with Tesla P4)
|
|
|
|
actual_devid == 0x1c03 || // GTX 1060 6GB
|
|
|
|
actual_devid == 0x1c04 || // GTX 1060 5GB
|
|
|
|
actual_devid == 0x1c02 || // GTX 1060 3GB
|
|
|
|
actual_devid == 0x1c07 || // P106-100 6GB
|
|
|
|
actual_devid == 0x1c09 || // P106-90 3GB
|
|
|
|
actual_devid == 0x1c22 || // GTX 1050 Mobile
|
|
|
|
actual_devid == 0x1c23 || // GTX 1060 Mobile Rev. 2
|
|
|
|
actual_devid == 0x1c20 || // GTX 1060 Mobile
|
|
|
|
actual_devid == 0x1c21 || // GTX 1050 Ti Mobile
|
|
|
|
actual_devid == 0x1c2d || // GP106M Generic
|
|
|
|
actual_devid == 0x1c60 || // GTX 1060 Mobile 6GB
|
|
|
|
actual_devid == 0x1c61 || // GTX 1050 Ti Mobile
|
|
|
|
actual_devid == 0x1c62 || // GTX 1050 Mobile
|
|
|
|
actual_devid == 0x1c70 || // GP106GL Generic
|
2021-04-12 10:10:28 +12:00
|
|
|
actual_devid == 0x1c30 || // Quadro P2000
|
|
|
|
actual_devid == 0x1c31 || // Quadro P2200
|
2021-04-14 06:04:16 +12:00
|
|
|
|
2021-01-29 10:22:28 +13:00
|
|
|
// GP104
|
2021-04-12 10:10:28 +12:00
|
|
|
actual_devid == 0x1b80 || // GTX 1080
|
2021-01-29 10:22:28 +13:00
|
|
|
actual_devid == 0x1b81 || // GTX 1070
|
|
|
|
actual_devid == 0x1b82 || // GTX 1070 Ti
|
2021-04-12 10:10:28 +12:00
|
|
|
actual_devid == 0x1b83 || // GTX 1060 6GB GP104 Refresh
|
|
|
|
actual_devid == 0x1b84 || // GTX 1060 3GB GP104 Refresh
|
2021-04-14 06:04:16 +12:00
|
|
|
actual_devid == 0x1b87 || // P104-100 Mining Card
|
|
|
|
actual_devid == 0x1ba0 || // GTX 1080 Mobile
|
|
|
|
actual_devid == 0x1ba1 || // GTX 1070 Mobile
|
|
|
|
actual_devid == 0x1bb0) { // Quadro P5000 (This will be moved to Tesla P6 in the future)
|
2021-01-29 10:22:28 +13:00
|
|
|
spoofed_devid = 0x1bb3; // Tesla P4
|
|
|
|
}
|
|
|
|
|
2021-04-12 10:10:28 +12:00
|
|
|
// GV100 (For the one person who owns a Titan Volta)
|
|
|
|
if(actual_devid == 0x1d81 || // TITAN V
|
2021-04-14 06:04:16 +12:00
|
|
|
actual_devid == 0x1dba) { // Quadro GV100 32GB
|
2021-04-12 10:10:28 +12:00
|
|
|
spoofed_devid = 0x1db4; // Tesla V100
|
|
|
|
}
|
|
|
|
|
2021-01-29 10:22:28 +13:00
|
|
|
// TU102
|
|
|
|
if(actual_devid == 0x1e02 || // TITAN RTX
|
|
|
|
actual_devid == 0x1e04 || // RTX 2080 Ti
|
2021-02-24 07:32:08 +13:00
|
|
|
actual_devid == 0x1e07) { // RTX 2080 Ti Rev. A
|
2021-01-29 10:22:28 +13:00
|
|
|
spoofed_devid = 0x1e30; // Quadro RTX 6000
|
2021-02-28 05:15:17 +13:00
|
|
|
spoofed_subsysid = 0x12ba;
|
2021-01-29 10:22:28 +13:00
|
|
|
}
|
|
|
|
|
2021-04-14 06:04:16 +12:00
|
|
|
// TU117 (Merged with Tesla T4)
|
|
|
|
if(actual_devid == 0x1ff9 || // Quadro T1000 Mobile
|
|
|
|
actual_devid == 0x1f99 || // TU1117 Mobile Unknown
|
|
|
|
actual_devid == 0x1fae || // TU1117GL Unknown
|
|
|
|
actual_devid == 0x1fb8 || // Quadro T2000 Mobile Max-Q
|
|
|
|
actual_devid == 0x1fb9 || // Quadro T1000 Mobile
|
|
|
|
actual_devid == 0x1fbf || // TU1117GL Unknown
|
|
|
|
actual_devid == 0x1f97 || // GeForce MX450
|
|
|
|
actual_devid == 0x1f98 || // GeForce MX450
|
|
|
|
actual_devid == 0x1f9c || // GeForce MX450
|
|
|
|
actual_devid == 0x1fbb || // Quadro T500 Mobile
|
|
|
|
actual_devid == 0x1fd9 || // GeForce GTX 1650 Mobile Refresh
|
|
|
|
actual_devid == 0x1f81 || // TU117 Unknown
|
|
|
|
actual_devid == 0x1f82 || // GeForce GTX 1650
|
|
|
|
actual_devid == 0x1f91 || // GTX 1650 Mobile Max-Q
|
|
|
|
actual_devid == 0x1f92 || // GTX 1650 Mobile
|
|
|
|
actual_devid == 0x1f94 || // GTX 1650 Mobile
|
|
|
|
actual_devid == 0x1f95 || // GTX 1650 Ti Mobile
|
|
|
|
actual_devid == 0x1f96 || // GTX 1650 Mobile Max-Q
|
|
|
|
|
|
|
|
// TU116 (Merged with Tesla T4)
|
|
|
|
actual_devid == 0x2182 || // GTX 1660 Ti
|
|
|
|
actual_devid == 0x2183 || // TU116 Unknown
|
|
|
|
actual_devid == 0x2184 || // GTX 1660
|
|
|
|
actual_devid == 0x2187 || // GTX 1650 SUPER
|
|
|
|
actual_devid == 0x2188 || // GTX 1650
|
|
|
|
actual_devid == 0x2191 || // GTX 1660 Ti Mobile
|
|
|
|
actual_devid == 0x2192 || // GTX 1650 Ti Mobile
|
|
|
|
actual_devid == 0x21ae || // TU116GL Unknown
|
|
|
|
actual_devid == 0x21bf || // TU116GL Unknown
|
|
|
|
actual_devid == 0x21c4 || // GTX 1660 Super
|
|
|
|
actual_devid == 0x21d1 || // GTX 1660 Ti Mobile
|
|
|
|
|
|
|
|
// TU106 (Merged with Tesla T4)
|
|
|
|
actual_devid == 0x1f02 || // RTX 2070 8GB
|
|
|
|
actual_devid == 0x1f04 || // TU106 Unknown
|
|
|
|
actual_devid == 0x1f06 || // RTX 2060 SUPER
|
|
|
|
actual_devid == 0x1f07 || // RTX 2070 Rev. A
|
|
|
|
actual_devid == 0x1f08 || // RTX 2060 6GB
|
|
|
|
actual_devid == 0x1f09 || // GTX 1660 Super
|
|
|
|
actual_devid == 0x1f0a || // GTX 1650
|
|
|
|
actual_devid == 0x1f10 || // RTX 2070 Mobile
|
|
|
|
actual_devid == 0x1f11 || // RTX 2060 Mobile
|
|
|
|
actual_devid == 0x1f12 || // RTX 2060 Mobile Max-Q
|
|
|
|
actual_devid == 0x1f14 || // RTX 2070 Mobile Max-Q
|
|
|
|
actual_devid == 0x1f15 || // RTX 2060 Mobile
|
|
|
|
actual_devid == 0x1f2e || // TU106M Mobile Unknown
|
|
|
|
actual_devid == 0x1f36 || // TU106GLM Mobile Unknown
|
|
|
|
actual_devid == 0x1f42 || // RTX 2060 SUPER
|
|
|
|
actual_devid == 0x1f47 || // RTX 2060 SUPER
|
|
|
|
actual_devid == 0x1f50 || // RTX 2070 Mobile
|
|
|
|
actual_devid == 0x1f51 || // RTX 2060 Mobile
|
|
|
|
|
2021-02-24 07:32:08 +13:00
|
|
|
// TU104
|
2021-04-14 06:04:16 +12:00
|
|
|
actual_devid == 0x1e81 || // RTX 2080 Super
|
2021-02-24 07:32:08 +13:00
|
|
|
actual_devid == 0x1e82 || // RTX 2080
|
|
|
|
actual_devid == 0x1e84 || // RTX 2070 Super
|
|
|
|
actual_devid == 0x1e87 || // RTX 2080 Rev. A
|
|
|
|
actual_devid == 0x1e89 || // RTX 2060
|
|
|
|
actual_devid == 0x1eb0 || // Quadro RTX 5000
|
|
|
|
actual_devid == 0x1eb1) { // Quadro RTX 4000
|
|
|
|
spoofed_devid = 0x1eb8; // Tesla T4
|
|
|
|
}
|
|
|
|
|
|
|
|
// GA102
|
|
|
|
if(actual_devid == 0x2204 || // RTX 3090
|
|
|
|
actual_devid == 0x2205 || // RTX 3080 Ti
|
|
|
|
actual_devid == 0x2206) { // RTX 3080
|
|
|
|
spoofed_devid = 0x2235; // RTX A40
|
|
|
|
}
|
|
|
|
|
2021-01-29 10:22:28 +13:00
|
|
|
devid_ptr.writeU16(spoofed_devid);
|
2021-02-28 05:15:17 +13:00
|
|
|
subsysid_ptr.writeU16(spoofed_subsysid);
|
2021-01-29 10:22:28 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
if(op_type == OP_READ_DEV_TYPE) {
|
|
|
|
// Set device type to vGPU capable.
|
|
|
|
var dev_type_ptr = this.argp.add(0x10).readPointer();
|
|
|
|
dev_type_ptr.writeU64(DEV_TYPE_VGPU_CAPABLE);
|
|
|
|
}
|
2021-04-18 03:37:07 +12:00
|
|
|
|
|
|
|
if(status != STATUS_OK) {
|
|
|
|
// Things seems to work fine even if some operations that fail
|
|
|
|
// result in failed assertions. So here we change the status
|
|
|
|
// value for these cases to cleanup the logs for nvidia-vgpu-mgr.
|
|
|
|
if(op_type == 0xA0820104 ||
|
|
|
|
op_type == 0x90960103) {
|
|
|
|
this.argp.add(0x1C).writeU32(STATUS_OK);
|
|
|
|
} else {
|
|
|
|
syslog("op_type: 0x" + op_type.toString(16) + " failed.");
|
|
|
|
}
|
|
|
|
}
|
2021-01-29 10:22:28 +13:00
|
|
|
}
|
|
|
|
});
|
2021-04-18 03:37:07 +12:00
|
|
|
|
|
|
|
syslog("vgpu_unlock loaded.");
|
2021-01-29 10:22:28 +13:00
|
|
|
"""
|
|
|
|
|
|
|
|
device = frida.get_local_device()
|
|
|
|
child_processes = queue.Queue()
|
|
|
|
|
|
|
|
def instrument(pid):
|
|
|
|
"""Instrument and resume process.
|
|
|
|
|
|
|
|
:param pid: Process identifier
|
|
|
|
"""
|
|
|
|
|
|
|
|
session = device.attach(pid)
|
|
|
|
# We need to also instrument the children since nvidia-vgpud forks itself
|
|
|
|
# when initially launched.
|
|
|
|
session.enable_child_gating()
|
|
|
|
script = session.create_script(script_source)
|
|
|
|
script.load()
|
|
|
|
device.resume(pid)
|
|
|
|
|
|
|
|
|
|
|
|
def on_child_added(child):
|
|
|
|
"""Callback for when a new child process has been created.
|
|
|
|
|
|
|
|
:param child: The newly created child process.
|
|
|
|
"""
|
|
|
|
|
|
|
|
child_processes.put(child.pid)
|
|
|
|
instrument(child.pid)
|
|
|
|
|
|
|
|
|
|
|
|
def wait_exit(pid):
|
|
|
|
"""Wait for a process to terminate.
|
|
|
|
|
|
|
|
:param pid: Process ID of the target process.
|
|
|
|
"""
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
time.sleep(.1)
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.kill(pid, 0)
|
|
|
|
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno == errno.ESRCH:
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
"""Entrypoint."""
|
|
|
|
|
|
|
|
# Behave at least a little bit like a forking service.
|
|
|
|
if sys.argv[1] != "-f":
|
|
|
|
subprocess.Popen([sys.argv[0], "-f"] + sys.argv[1:])
|
|
|
|
exit()
|
|
|
|
|
|
|
|
device.on("child-added", on_child_added)
|
2021-04-16 03:50:06 +12:00
|
|
|
pid = device.spawn(["/bin/bash", "-c", ' '.join(sys.argv[2:])])
|
2021-01-29 10:22:28 +13:00
|
|
|
instrument(pid)
|
|
|
|
|
|
|
|
# Wait for everything to terminate before exiting.
|
|
|
|
wait_exit(pid)
|
|
|
|
|
|
|
|
while not child_processes.empty():
|
|
|
|
wait_exit(child_processes.get_nowait())
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|
|
|
|
|