diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..75c966b --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,15 @@ +name: Test +on: [push, pull_request] + +jobs: + build: + name: NickelMenu / Symbols + runs-on: ubuntu-latest + container: docker.io/golang:1.14 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Build + run: cd test/syms && go build -o ../../test.syms . + - name: Run + run: cd src && ../test.syms diff --git a/res/doc b/res/doc index e078805..be82184 100644 --- a/res/doc +++ b/res/doc @@ -39,10 +39,10 @@ # This is actually the basename of the watch's filename as specified in its KFMon config (i.e., the png). # You can also check the output of the 'list' command via the kfmon-ipc tool. # nickel_setting - one of: -# invert - toggles FeatureSettings.InvertScreen (all versions) +# invert - toggles FeatureSettings.InvertScreen # lockscreen - toggles PowerSettings.UnlockEnabled (4.12.12111+) -# screenshots - toggles FeatureSettings.Screenshots (all versions) -# force_wifi - toggles DeveloperSettings.ForceWifiOn (note: the setting doesn't apply until you toggle WiFi) (all versions) +# screenshots - toggles FeatureSettings.Screenshots +# force_wifi - toggles DeveloperSettings.ForceWifiOn (note: the setting doesn't apply until you toggle WiFi) # nickel_extras - the mimetype of the plugin, or one of: # web_browser # unblock_it @@ -52,11 +52,11 @@ # word_scramble # nickel_misc - one of: # force_usb_connection - forces a usb connection dialog to be shown -# rescan_books - forces nickel to rescan books -# rescan_books_full - forces a full usb connect/disconnect cycle +# rescan_books - forces nickel to rescan books (4.13.12638+) +# rescan_books_full - forces a full usb connect/disconnect cycle (4.13.12638+) # power - one of: -# shutdown -# reboot +# shutdown (4.13.12638+) +# reboot (4.13.12638+) # cmd_spawn - the command line to pass to /bin/sh -c (started in /) # It can be prefixed with "quiet:" to prevent the toast with the process PID from being displayed. # cmd_output - the timeout in milliseconds (0 < t < 10000), a colon, then the command line to pass to /bin/sh -c (started in /) diff --git a/src/action_cc.cc b/src/action_cc.cc index b9385a7..aa943b2 100644 --- a/src/action_cc.cc +++ b/src/action_cc.cc @@ -54,26 +54,31 @@ typedef void N3PowerWorkflowManager; NM_ACTION_(nickel_setting) { #define NM_ERR_RET nullptr + //libnickel 4.6 * _ZN6Device16getCurrentDeviceEv Device *(*Device_getCurrentDevice)(); reinterpret_cast(Device_getCurrentDevice) = dlsym(RTLD_DEFAULT, "_ZN6Device16getCurrentDeviceEv"); NM_ASSERT(Device_getCurrentDevice, "could not dlsym Device::getCurrentDevice"); + //libnickel 4.6 * _ZN8SettingsC2ERK6Deviceb _ZN8SettingsC2ERK6Device void *(*Settings_Settings)(Settings*, Device*, bool); void *(*Settings_SettingsLegacy)(Settings*, Device*); reinterpret_cast(Settings_Settings) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Deviceb"); reinterpret_cast(Settings_SettingsLegacy) = dlsym(RTLD_DEFAULT, "_ZN8SettingsC2ERK6Device"); NM_ASSERT(Settings_Settings || Settings_SettingsLegacy, "could not dlsym Settings constructor (new and/or old)"); + //libnickel 4.6 * _ZN8SettingsD2Ev void *(*Settings_SettingsD)(Settings*); reinterpret_cast(Settings_SettingsD) = dlsym(RTLD_DEFAULT, "_ZN8SettingsD2Ev"); NM_ASSERT(Settings_SettingsD, "could not dlsym Settings destructor"); // some settings don't have symbols in a usable form, and some are inlined, so we may need to set them directly + //libnickel 4.6 * _ZN8Settings10getSettingERK7QStringRK8QVariant QVariant (*Settings_getSetting)(Settings*, QString const&, QVariant const&); // the last param is the default, also note that this requires a subclass of Settings reinterpret_cast(Settings_getSetting) = dlsym(RTLD_DEFAULT, "_ZN8Settings10getSettingERK7QStringRK8QVariant"); NM_ASSERT(Settings_getSetting, "could not dlsym Settings::getSetting"); // ditto + //libnickel 4.6 * _ZN8Settings11saveSettingERK7QStringRK8QVariantb void *(*Settings_saveSetting)(Settings*, QString const&, QVariant const&, bool); // the last param is whether to do a full disk sync immediately (rather than waiting for the kernel to do it) reinterpret_cast(Settings_saveSetting) = dlsym(RTLD_DEFAULT, "_ZN8Settings11saveSettingERK7QStringRK8QVariantb"); NM_ASSERT(Settings_saveSetting, "could not dlsym Settings::saveSetting"); @@ -106,6 +111,7 @@ NM_ACTION_(nickel_setting) { #define vtable_ptr(x) *reinterpret_cast(settings) #define vtable_target(x) reinterpret_cast(reinterpret_cast(x)+8) + //libnickel 4.6 * _ZTV8Settings void *Settings_vtable = dlsym(RTLD_DEFAULT, "_ZTV8Settings"); NM_ASSERT(Settings_vtable, "could not dlsym the vtable for Settings"); NM_ASSERT(vtable_ptr(settings) == vtable_target(Settings_vtable), "unexpected vtable layout (expected class to start with a pointer to 8 bytes into the vtable)"); @@ -113,15 +119,18 @@ NM_ACTION_(nickel_setting) { bool v = false; if (!strcmp(arg, "invert") || !strcmp(arg, "screenshots")) { + //libnickel 4.6 * _ZTV15FeatureSettings void *FeatureSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV15FeatureSettings"); NM_ASSERT(FeatureSettings_vtable, "could not dlsym the vtable for FeatureSettings"); vtable_ptr(settings) = vtable_target(FeatureSettings_vtable); if (!strcmp(arg, "invert")) { + //libnickel 4.6 * _ZN15FeatureSettings12invertScreenEv bool (*FeatureSettings_invertScreen)(Settings*); reinterpret_cast(FeatureSettings_invertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings12invertScreenEv"); NM_ASSERT(FeatureSettings_invertScreen, "could not dlsym FeatureSettings::invertScreen"); + //libnickel 4.6 * _ZN15FeatureSettings15setInvertScreenEb bool (*FeatureSettings_setInvertScreen)(Settings*, bool); reinterpret_cast(FeatureSettings_setInvertScreen) = dlsym(RTLD_DEFAULT, "_ZN15FeatureSettings15setInvertScreenEb"); NM_ASSERT(FeatureSettings_setInvertScreen, "could not dlsym FeatureSettings::setInvertScreen"); @@ -157,10 +166,12 @@ NM_ACTION_(nickel_setting) { vtable_ptr(settings) = vtable_target(PowerSettings_vtable); if (!strcmp(arg, "lockscreen")) { + //libnickel 4.12.12111 * _ZN13PowerSettings16getUnlockEnabledEv bool (*PowerSettings__getUnlockEnabled)(Settings*); reinterpret_cast(PowerSettings__getUnlockEnabled) = dlsym(RTLD_DEFAULT, "_ZN13PowerSettings16getUnlockEnabledEv"); NM_ASSERT(PowerSettings__getUnlockEnabled, "could not dlsym PowerSettings::getUnlockEnabled"); + //libnickel 4.12.12111 * _ZN13PowerSettings16setUnlockEnabledEb bool (*PowerSettings__setUnlockEnabled)(Settings*, bool); reinterpret_cast(PowerSettings__setUnlockEnabled) = dlsym(RTLD_DEFAULT, "_ZN13PowerSettings16setUnlockEnabledEb"); NM_ASSERT(PowerSettings__setUnlockEnabled, "could not dlsym PowerSettings::setUnlockEnabled"); @@ -175,6 +186,7 @@ NM_ACTION_(nickel_setting) { vtable_ptr(settings) = vtable_target(PowerSettings_vtable); } } else if (!strcmp(arg, "force_wifi")) { + //libnickel 4.6 * _ZTV11DevSettings void *PowerSettings_vtable = dlsym(RTLD_DEFAULT, "_ZTV11DevSettings"); NM_ASSERT(PowerSettings_vtable, "could not dlsym the vtable for DevSettings"); vtable_ptr(settings) = vtable_target(PowerSettings_vtable); @@ -212,14 +224,17 @@ NM_ACTION_(nickel_extras) { #define NM_ERR_RET nullptr if (!strcmp(arg, "web_browser")) { + //libnickel 4.6 * _ZN26N3SettingsExtrasControllerC2Ev void (*N3SettingsExtrasController_N3SettingsExtrasController)(N3SettingsExtrasController*); reinterpret_cast(N3SettingsExtrasController_N3SettingsExtrasController) = dlsym(RTLD_DEFAULT, "_ZN26N3SettingsExtrasControllerC2Ev"); NM_ASSERT(N3SettingsExtrasController_N3SettingsExtrasController, "could not dlsym N3SettingsExtrasController constructor"); + //libnickel 4.6 * _ZN26N3SettingsExtrasControllerD1Ev void (*N3SettingsExtrasController_N3SettingsExtrasControllerD)(N3SettingsExtrasController*); reinterpret_cast(N3SettingsExtrasController_N3SettingsExtrasControllerD) = dlsym(RTLD_DEFAULT, "_ZN26N3SettingsExtrasControllerD1Ev"); NM_ASSERT(N3SettingsExtrasController_N3SettingsExtrasControllerD, "could not dlsym N3SettingsExtrasController destructor"); + //libnickel 4.6 * _ZN26N3SettingsExtrasController11openBrowserEv void (*N3SettingsExtrasController_openBrowser)(N3SettingsExtrasController*); reinterpret_cast(N3SettingsExtrasController_openBrowser) = dlsym(RTLD_DEFAULT, "_ZN26N3SettingsExtrasController11openBrowserEv"); NM_ASSERT(N3SettingsExtrasController_openBrowser, "could not dlsym BrowserWorkflowManager::openBrowser"); @@ -230,17 +245,21 @@ NM_ACTION_(nickel_extras) { N3SettingsExtrasController_N3SettingsExtrasControllerD(nse); // the QObject is only used to pass events to it, but there's something I'm missing here which leads to it segfaulting after connecting to WiFi if it isn't already connected - /*void (*BrowserWorkflowManager_BrowserWorkflowManager)(BrowserWorkflowManager*, QObject*); + /* + //libnickel 4.11.11911 * _ZN22BrowserWorkflowManagerC1EP7QObject + void (*BrowserWorkflowManager_BrowserWorkflowManager)(BrowserWorkflowManager*, QObject*); reinterpret_cast(BrowserWorkflowManager_BrowserWorkflowManager) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManagerC1EP7QObject"); NM_ASSERT(BrowserWorkflowManager_BrowserWorkflowManager, "could not dlsym BrowserWorkflowManager constructor"); + //libnickel 4.6 * _ZN22BrowserWorkflowManager11openBrowserEbRK4QUrlRK7QString void (*BrowserWorkflowManager_openBrowser)(BrowserWorkflowManager*, bool, QUrl const&, QString const&); // the bool is whether to open it as a modal, the string is CSS to inject reinterpret_cast(BrowserWorkflowManager_openBrowser) = dlsym(RTLD_DEFAULT, "_ZN22BrowserWorkflowManager11openBrowserEbRK4QUrlRK7QString"); NM_ASSERT(BrowserWorkflowManager_openBrowser, "could not dlsym BrowserWorkflowManager::openBrowser"); BrowserWorkflowManager *bwm = alloca(128); // as of 4.20.14622, it's actually 20 bytes, but we're going to stay on the safe side BrowserWorkflowManager_BrowserWorkflowManager(bwm, new QObject()); - BrowserWorkflowManager_openBrowser(bwm, false, QUrl(), QStringLiteral("")); // if !QUrl::isValid(), it loads the homepage*/ + BrowserWorkflowManager_openBrowser(bwm, false, QUrl(), QStringLiteral("")); // if !QUrl::isValid(), it loads the homepage + */ NM_RETURN_OK(nm_action_result_silent()); } @@ -253,6 +272,7 @@ NM_ACTION_(nickel_extras) { else if (!strcmp(arg, "word_scramble")) mimetype = "application/x-games-Boggle"; else NM_RETURN_ERR("unknown beta feature name or plugin mimetype '%s'", arg); + //libnickel 4.6 * _ZN18ExtrasPluginLoader10loadPluginEPKc void (*ExtrasPluginLoader_loadPlugin)(const char*); reinterpret_cast(ExtrasPluginLoader_loadPlugin) = dlsym(RTLD_DEFAULT, "_ZN18ExtrasPluginLoader10loadPluginEPKc"); NM_ASSERT(ExtrasPluginLoader_loadPlugin, "could not dlsym ExtrasPluginLoader::loadPlugin"); @@ -265,10 +285,12 @@ NM_ACTION_(nickel_extras) { NM_ACTION_(nickel_misc) { #define NM_ERR_RET nullptr if (!strcmp(arg, "rescan_books")) { + //libnickel 4.13.12638 * _ZN19PlugWorkflowManager14sharedInstanceEv PlugWorkflowManager *(*PlugWorkflowManager_sharedInstance)(); reinterpret_cast(PlugWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager14sharedInstanceEv"); NM_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance"); + //libnickel 4.13.12638 * _ZN19PlugWorkflowManager4syncEv void (*PlugWorkflowManager_sync)(PlugWorkflowManager*); reinterpret_cast(PlugWorkflowManager_sync) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager4syncEv"); NM_ASSERT(PlugWorkflowManager_sync, "could not dlsym PlugWorkflowManager::sync"); @@ -278,15 +300,18 @@ NM_ACTION_(nickel_misc) { PlugWorkflowManager_sync(wf); } else if (!strcmp(arg, "rescan_books_full")) { + //libnickel 4.13.12638 * _ZN19PlugWorkflowManager14sharedInstanceEv PlugWorkflowManager *(*PlugWorkflowManager_sharedInstance)(); reinterpret_cast(PlugWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager14sharedInstanceEv"); NM_ASSERT(PlugWorkflowManager_sharedInstance, "could not dlsym PlugWorkflowManager::sharedInstance"); // this is what is called by PlugWorkflowManager::plugged after confirmation + //libnickel 4.13.12638 * _ZN19PlugWorkflowManager18onCancelAndConnectEv void (*PlugWorkflowManager_onCancelAndConnect)(PlugWorkflowManager*); reinterpret_cast(PlugWorkflowManager_onCancelAndConnect) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager18onCancelAndConnectEv"); NM_ASSERT(PlugWorkflowManager_onCancelAndConnect, "could not dlsym PlugWorkflowManager::onCancelAndConnect"); + //libnickel 4.13.12638 * _ZN19PlugWorkflowManager9unpluggedEv void (*PlugWorkflowManager_unplugged)(PlugWorkflowManager*); reinterpret_cast(PlugWorkflowManager_unplugged) = dlsym(RTLD_DEFAULT, "_ZN19PlugWorkflowManager9unpluggedEv"); NM_ASSERT(PlugWorkflowManager_unplugged, "could not dlsym PlugWorkflowManager::unplugged"); @@ -316,6 +341,7 @@ NM_ACTION_(nickel_misc) { NM_ACTION_(power) { #define NM_ERR_RET nullptr if (!strcmp(arg, "shutdown") || !strcmp(arg, "reboot")) { + //libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager14sharedInstanceEv N3PowerWorkflowManager *(*N3PowerWorkflowManager_sharedInstance)(); reinterpret_cast(N3PowerWorkflowManager_sharedInstance) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager14sharedInstanceEv"); NM_ASSERT(N3PowerWorkflowManager_sharedInstance, "could not dlsym N3PowerWorkflowManager::sharedInstance, so cannot perform action cleanly (if you must, report a bug and use cmd_spawn instead)"); @@ -324,6 +350,7 @@ NM_ACTION_(power) { NM_ASSERT(pwm, "could not get shared power manager pointer"); if (!strcmp(arg, "shutdown")) { + //libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager8powerOffEb void (*N3PowerWorkflowManager_powerOff)(N3PowerWorkflowManager*, bool); // bool is for if it's due to low battery reinterpret_cast(N3PowerWorkflowManager_powerOff) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager8powerOffEb"); NM_ASSERT(N3PowerWorkflowManager_powerOff, "could not dlsym N3PowerWorkflowManager::powerOff"); @@ -331,6 +358,7 @@ NM_ACTION_(power) { N3PowerWorkflowManager_powerOff(pwm, false); NM_RETURN_OK(nm_action_result_toast("Shutting down...")); } else if (!strcmp(arg, "reboot")) { + //libnickel 4.13.12638 * _ZN22N3PowerWorkflowManager6rebootEv void (*N3PowerWorkflowManager_reboot)(N3PowerWorkflowManager*); reinterpret_cast(N3PowerWorkflowManager_reboot) = dlsym(RTLD_DEFAULT, "_ZN22N3PowerWorkflowManager6rebootEv"); NM_ASSERT(N3PowerWorkflowManager_reboot, "could not dlsym N3PowerWorkflowManager::reboot"); diff --git a/src/menu.cc b/src/menu.cc index 1df4d98..566b05c 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -50,10 +50,15 @@ static size_t _items_n; extern "C" int nm_menu_hook(void *libnickel, nm_menu_item_t **items, size_t items_n, char **err_out) { #define NM_ERR_RET 1 + //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ reinterpret_cast(AbstractNickelMenuController_createMenuTextItem) = dlsym(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_"); + //libnickel 4.6 * _ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb reinterpret_cast(AbstractNickelMenuController_createAction) = dlsym(libnickel, "_ZN22AbstractMenuController12createActionEP5QMenuP7QWidgetbbb"); + //libnickel 4.6 * _ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_ reinterpret_cast(ConfirmationDialogFactory_showOKDialog) = dlsym(libnickel, "_ZN25ConfirmationDialogFactory12showOKDialogERK7QStringS2_"); + //libnickel 4.6 * _ZN20MainWindowController14sharedInstanceEv reinterpret_cast(MainWindowController_sharedInstance) = dlsym(libnickel, "_ZN20MainWindowController14sharedInstanceEv"); + //libnickel 4.6 * _ZN20MainWindowController5toastERK7QStringS2_i reinterpret_cast(MainWindowController_toast) = dlsym(libnickel, "_ZN20MainWindowController5toastERK7QStringS2_i"); NM_ASSERT(AbstractNickelMenuController_createMenuTextItem, "unsupported firmware: could not find AbstractNickelMenuController::createMenuTextItem(void* _this, QMenu*, QString, bool, bool, QString const&)"); @@ -66,6 +71,7 @@ extern "C" int nm_menu_hook(void *libnickel, nm_menu_item_t **items, size_t item NM_ASSERT(nmh, "internal error: could not dlsym _nm_menu_hook"); char *err; + //libnickel 4.6 * _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_ reinterpret_cast(AbstractNickelMenuController_createMenuTextItem_orig) = nm_dlhook(libnickel, "_ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_", nmh, &err); NM_ASSERT(AbstractNickelMenuController_createMenuTextItem_orig, "failed to hook _ZN28AbstractNickelMenuController18createMenuTextItemEP5QMenuRK7QStringbbS4_: %s", err); diff --git a/test/syms/go.mod b/test/syms/go.mod new file mode 100644 index 0000000..58076d6 --- /dev/null +++ b/test/syms/go.mod @@ -0,0 +1,8 @@ +module github.com/geek1011/NickelMenu/test/syms + +go 1.14 + +require ( + github.com/geek1011/kobopatch v0.15.0 + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 +) diff --git a/test/syms/go.sum b/test/syms/go.sum new file mode 100644 index 0000000..62c8152 --- /dev/null +++ b/test/syms/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/geek1011/czlib v0.0.3 h1:Cp9hWIbzdvyj/QmpciCghWLbRpRaMpJS7wL7bTxwgUM= +github.com/geek1011/czlib v0.0.3/go.mod h1:iw913x/pjDqhfoak/AU3XWgycIcGL9H6kjgPP2YqzQM= +github.com/geek1011/kobopatch v0.15.0 h1:xIxJPHNoiwbD7VkwBmZRzQAVHfjPqxuEvFNtDfKGPiE= +github.com/geek1011/kobopatch v0.15.0/go.mod h1:4/PuLBLcPPNO20/gBRJh9VhdVyNZmuwCvo8wffcVWiQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/arm v0.0.0-20150420010332-9c32f2193064 h1:bBbas3KhLwE6f59Z9lUipY23xUX9qrvyLBdQzzV2Tko= +rsc.io/arm v0.0.0-20150420010332-9c32f2193064/go.mod h1:MVYPdlFruujBlzEY3x2Q3XBk7XLdYRNZ7zDbrzYFO7w= diff --git a/test/syms/main.go b/test/syms/main.go new file mode 100644 index 0000000..1beadd7 --- /dev/null +++ b/test/syms/main.go @@ -0,0 +1,276 @@ +package main + +import ( + "archive/tar" + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/geek1011/kobopatch/patchlib" + "github.com/xi2/xz" +) + +func main() { + sc, err := FindSymChecks(".") + if err != nil { + fmt.Fprintf(os.Stderr, "[FTL] find symbol checks: %v\n", err) + os.Exit(1) + return + } + + versions := []string{ + "4.6.9960", "4.6.9995", "4.7.10075", "4.7.10364", "4.7.10413", + "4.8.10956", "4.8.11073", "4.8.11090", "4.9.11311", "4.9.11314", + "4.10.11591", "4.10.11655", "4.11.11911", "4.11.11976", "4.11.11980", + "4.11.11982", "4.11.12019", "4.12.12111", "4.13.12638", "4.14.12777", + "4.15.12920", "4.16.13162", "4.17.13651", "4.17.13694", "4.18.13737", + "4.19.14123", "4.20.14601", "4.20.14617", "4.20.14622", + } + + checks := map[string]map[string][]SymCheck{} + for _, c := range sc { + var sm, em int + for _, version := range versions { + if c.StartVersion == "*" || strings.HasPrefix(version+".", c.StartVersion+".") { + sm++ + } + if c.EndVersion == "*" || strings.HasPrefix(version+".", c.EndVersion+".") { + em++ + } + if versioncmp(c.StartVersion, version) <= 0 && versioncmp(version, c.EndVersion) >= 0 { + if _, ok := checks[version]; !ok { + checks[version] = map[string][]SymCheck{} + } + checks[version][c.Library] = append(checks[version][c.Library], c) + } + } + if sm == 0 { + fmt.Printf("[WRN] %s: no exact match for the base version in specifier %#v\n", c.File, c.StartVersion) + } + if em == 0 { + fmt.Printf("[WRN] %s: no exact match for the base version in specifier %#v\n", c.File, c.EndVersion) + } + } + + var checkVersions []string + for version := range checks { + checkVersions = append(checkVersions, version) + } + sort.Slice(checkVersions, func(i, j int) bool { + return versioncmp(checkVersions[i], checkVersions[j]) == -1 + }) + + var errs []error + gherrs := map[string][]string{} + for _, version := range checkVersions { + var checkLibs []string + for lib := range checks[version] { + checkLibs = append(checkLibs, lib) + } + sort.Strings(checkLibs) + + for _, lib := range checkLibs { + fmt.Printf("[INF] checking %s@%s\n", lib, version) + + pt, err := GetPatcher(version, lib) + if err != nil { + fmt.Fprintf(os.Stderr, "[FTL] get patcher: %v\n", err) + os.Exit(1) + return + } else if pt == nil { + fmt.Printf("[WRN] no data available, skipping\n") + continue + } + + _, err = pt.ExtractDynsyms(true) + if err != nil { + fmt.Fprintf(os.Stderr, "[FTL] extract symbols: %v\n", err) + os.Exit(1) + return + } + + for _, check := range checks[version][lib] { + fmt.Printf("[INF] %s:\n checking for one of %+s\n", check.File, check.Symbols) + var f bool + for _, sym := range check.Symbols { + off, err := pt.ResolveSym(sym) + if err != nil { + fmt.Printf(" %s not found\n", sym) + } else { + fmt.Printf(" %s found at %#x\n", sym, off) + f = true + } + } + if !f { + err := fmt.Errorf("%s: one of %+s not found in %s@%s", check.File, check.Symbols, lib, version) + fmt.Printf("[ERR] %v\n", err) + errs = append(errs, err) + + spl := strings.Split(check.File, ":") + gherrf := fmt.Sprintf("file=%s,line=%s,col=%s", spl[0], spl[1], spl[2]) + gherrs[gherrf] = append(gherrs[gherrf], fmt.Sprintf("one of symbols %+s not found in %s@%s", check.Symbols, lib, version)) + } + } + } + } + if len(errs) == 0 { + os.Exit(0) + } + + fmt.Printf("[FTL] check failed\n") + for _, err := range errs { + fmt.Printf(" %v\n", err) + } + if os.Getenv("GITHUB_ACTIONS") == "true" { + var ghfs []string + for ghf := range gherrs { + ghfs = append(ghfs, ghf) + } + sort.Strings(ghfs) + for _, ghf := range ghfs { + fmt.Printf("::error %s::%s\n", ghf, strings.Join(gherrs[ghf], "%0A")) + } + } + os.Exit(1) +} + +func GetPatcher(version, lib string) (*patchlib.Patcher, error) { + resp, err := http.Get("https://github.com/geek1011/kobopatch-patches/raw/master/testdata/" + version + ".tar.xz") + if err != nil { + return nil, fmt.Errorf("get kobopatch testdata for %#v: %w", version, err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, nil + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("get kobopatch testdata for %#v: response status %s", version, resp.Status) + } + + zr, err := xz.NewReader(resp.Body, 0) + if err != nil { + return nil, fmt.Errorf("read kobopatch testdata: %w", err) + } + + tr := tar.NewReader(zr) + for { + th, err := tr.Next() + if err != nil { + if err == io.EOF { + return nil, fmt.Errorf("read kobopatch testdata: file %#v not found", lib) + } + return nil, fmt.Errorf("read kobopatch testdata: %w", err) + } + if filepath.Clean(th.Name) == filepath.Clean(lib) { + break + } + } + + buf, err := ioutil.ReadAll(tr) + if err != nil { + return nil, fmt.Errorf("read kobopatch testdata: %w", err) + } + return patchlib.NewPatcher(buf), nil +} + +type SymCheck struct { + File string + Library string + StartVersion string + EndVersion string + Symbols []string // or +} + +func FindSymChecks(dir string) ([]SymCheck, error) { + var checks []SymCheck + if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + var m bool + for _, ext := range []string{".c", ".cc", ".cpp", ".h"} { + if filepath.Ext(path) == ext { + m = true + break + } + } + if !m { + return nil + } + + f, err := os.OpenFile(path, os.O_RDONLY, 0) + if err != nil { + return fmt.Errorf("open %#v: %w", path, err) + } + defer f.Close() + + sc := bufio.NewScanner(f) + var line int + for sc.Scan() { + line++ + col := bytes.Index(sc.Bytes(), []byte("//libnickel")) + if col == -1 { + continue + } + + args := strings.Fields(string(bytes.TrimSpace(sc.Bytes()[col+len("//libnickel"):]))) + if len(args) < 3 || args[0] == "*" { + return fmt.Errorf("parse %#v: line %d, col %d: expected comment to be in the format '//libnickel ...'", path, line, col+1) + } + + checks = append(checks, SymCheck{ + File: fmt.Sprintf("%s:%d:%d", path, line, col+1), + Library: "libnickel.so.1.0.0", + StartVersion: args[0], + EndVersion: args[1], + Symbols: args[2:], + }) + } + if err := sc.Err(); err != nil { + return fmt.Errorf("read %#v: %w", path, err) + } + + return nil + }); err != nil { + return nil, err + } + return checks, nil +} + +func versioncmp(a, b string) int { + if a == "*" || b == "*" { + return 0 + } + aspl, bspl := splint(a), splint(b) + mlen := len(aspl) + if len(bspl) > mlen { + mlen = len(bspl) + } + for i := 0; i < mlen; i++ { + switch { + case i == len(bspl): + return 1 + case i == len(aspl): + return -1 + case aspl[i] > bspl[i]: + return 1 + case bspl[i] > aspl[i]: + return -1 + } + } + return 0 +} + +func splint(str string) []int64 { + spl := strings.Split(str, ".") + ints := make([]int64, len(spl)) + for i, p := range spl { + ints[i], _ = strconv.ParseInt(p, 10, 64) + } + return ints +}