From 9497a99a8d76d496d94e061654bc2edefc401785 Mon Sep 17 00:00:00 2001 From: Josh Watson Date: Tue, 18 Sep 2018 20:16:58 -0700 Subject: Fix generation of core.BNLog Most of the BNLog API methods are just variable length arguments of strings. BNLog also includes a `level` parameter, which causes a `TypeError` when `binaryninja.log.log(level, msg)` is called, because it is expecting all parameters to be strings. This PR fixes it so that `BNLog` is correctly generated. Note: I haven't actually tested this to verify `generator` still works, because my Makefile is messed up currently. --- python/generator.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/python/generator.cpp b/python/generator.cpp index 39e45aea..2b2f0e91 100644 --- a/python/generator.cpp +++ b/python/generator.cpp @@ -396,9 +396,18 @@ int main(int argc, char* argv[]) // As of writing this, only BNLog's have variable instruction lengths, but in an attempt not to break in the future: if (funcName.compare(0, 6, "_BNLog") == 0) { - fprintf(out, "def %s(*args):\n", name.c_str()); - fprintf(out, "\treturn %s(*[cstr(arg) for arg in args])\n\n", funcName.c_str()); - continue; + if (funcName != "_BNLog") + { + fprintf(out, "def %s(*args):\n", name.c_str()); + fprintf(out, "\treturn %s(*[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } + else + { + fprintf(out, "def %s(level, *args):\n", name.c_str()); + fprintf(out, "\treturn %s(level, *[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } } } -- cgit v1.3.1 From 165145177f85efc03f7945b64ab55078d5c1fcfb Mon Sep 17 00:00:00 2001 From: Kyle Martin Date: Mon, 24 Sep 2018 11:43:40 -0400 Subject: Remove extraneous text --- docs/getting-started.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 59695147..0fa6098a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -280,12 +280,7 @@ Settings are stored in the _user_ directory in the file `settings.json`. Each to | arch | x86.disassemblyFlavor | string | "BN_INTEL" | "BN_INTEL", "INTEL", or "AT&T" | | arch | x86.disassemblySeperator | string | ", " | What to put between operands in disassembly tokens | | arch | x86.disassemblyLowercase | bool | True | Lowercase opcodes, operands, and registers (False for uppercase) | - "arch" : - { - // "x86.disassemblyFlavor" : "AT&T", - // "x86.disassemblyFlavor" : "BN_INTEL", - "x86.disassemblyLowercase" : true, - "x86.disassemblySeperator" : ", " + Below is an example `settings.json` setting various options: ``` { -- cgit v1.3.1 From 674336c1a5928a9b84165256f9d6e2bdf6568296 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Mon, 24 Sep 2018 11:41:04 -0400 Subject: documentation updates --- docs/getting-started.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 0fa6098a..bbbb42a3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -276,10 +276,10 @@ Settings are stored in the _user_ directory in the file `settings.json`. Each to | pdb | local-store-relative | string | "symbols" | Path *relative* to the binaryninja _user_ directory, sepcifying the pdb symbol store | | pdb | auto-download-pdb | boolean | True | Automatically download pdb files from specified symbol servers | | pdb | symbol-server-list | list(string) | ["http://msdl.microsoft.com/download/symbols"] | List of servers to query for pdb symbols. | -| python | interpreter | string | "python27.{dylib,dll,so.1}" | Python interpreter to load if one is not already present when plugins are loaded | -| arch | x86.disassemblyFlavor | string | "BN_INTEL" | "BN_INTEL", "INTEL", or "AT&T" | -| arch | x86.disassemblySeperator | string | ", " | What to put between operands in disassembly tokens | -| arch | x86.disassemblyLowercase | bool | True | Lowercase opcodes, operands, and registers (False for uppercase) | +| python | interpreter | string | "{/path/,C:\\Path\\}python27.{dylib,dll,so.1}" | Python interpreter to load if one is not already present when plugins are loaded | +| arch | x86.disassemblyFlavor | string | "BN_INTEL" | "BN_INTEL", "INTEL", or "AT&T" | +| arch | x86.disassemblySeperator | string | ", " | What to put between operands in disassembly tokens | +| arch | x86.disassemblyLowercase | bool | True | Lowercase opcodes, operands, and registers (False for uppercase) | Below is an example `settings.json` setting various options: ``` @@ -288,15 +288,19 @@ Below is an example `settings.json` setting various options: { "activeContent" : false, "colorblind" : false, - "debug" : true + "debug" : true, "recent-file-limit" : 10 } "pdb" : { - "local-store-absolute" : "C:\Symbols", + "local-store-absolute" : "C:\\Symbols", "local-store-relative" : "", "symbol-server-list" : ["http://mysymbolserver.company.lan"] - } + }, + "python": + { + "interpreter": "C:\\Users\\Binja\\AppData\\Local\\Programs\\Python\\Python37\\python37.dll" + } } ``` -- cgit v1.3.1 From c28dc23feb931a290e9734b68e1af18e3a38c1a2 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Mon, 24 Sep 2018 11:49:12 -0400 Subject: table cleanup and extra escaping for windows path --- docs/getting-started.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index bbbb42a3..1a02a3a0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -257,29 +257,29 @@ On windows, this is achieved through a separate launcher that loads first and re Settings are stored in the _user_ directory in the file `settings.json`. Each top level object in this file is represents a different plugin. As of build 860 the following settings are available: -|Plugin | Setting | Type | Default | Description | -|----------:|-------------------------:|-------------:|-----------------------------------------------:|:----------------------------------------------------------------------------------------------| -| analysis | autorunLinearSweep | boolean | True | Automatically run linear sweep when opening a binary for analysis | -| analysis | enabledUnicodeBlocks | list(string) | [] | Defines which Unicode blocks to consider when searching for strings | -| analysis | enableUTF8 | boolean | True | Whether or not to consider UTF-8 code points when searching for strings | -| analysis | enableUTF16 | boolean | True | Whether or not to consider UTF-16 code points when searching for strings | -| analysis | enableUTF32 | boolean | True | Whether or not to consider UTF-32 code points when searching for strings | -| analysis | max-function-size | integer | 65536 | Any functions over this size will not be automatically analyzed and require manual override | -| core | linux\_ca\_bundle | string | "" | Certificate authority (.pem or .crt) file to be used for secure downloads | -| core | linux\_ca\_dir | string | "" | Certificate authority directory (for distributions without a CA bundle) | -| ui | activeContent | boolean | True | Allow Binary Ninja to connect to the web to check for updates | -| ui | colorblind | boolean | True | Choose colors that are visible to those with red/green colorblind | -| ui | debug | boolean | False | Enable developer debugging features (Additional views: Lifted IL, and SSA forms) | -| ui | recent-file-limit | integer | 10 | Specify limit for number of recent files | -| ui | scriptingProvider | string | "Python" | Specify the registered ScriptingProvider that controls the 'Console' in the UI | -| pdb | local-store-absolute | string | "" | Absolute path specifying where the pdb symbol store exists on this machine, overrides relative path | -| pdb | local-store-relative | string | "symbols" | Path *relative* to the binaryninja _user_ directory, sepcifying the pdb symbol store | -| pdb | auto-download-pdb | boolean | True | Automatically download pdb files from specified symbol servers | -| pdb | symbol-server-list | list(string) | ["http://msdl.microsoft.com/download/symbols"] | List of servers to query for pdb symbols. | -| python | interpreter | string | "{/path/,C:\\Path\\}python27.{dylib,dll,so.1}" | Python interpreter to load if one is not already present when plugins are loaded | -| arch | x86.disassemblyFlavor | string | "BN_INTEL" | "BN_INTEL", "INTEL", or "AT&T" | -| arch | x86.disassemblySeperator | string | ", " | What to put between operands in disassembly tokens | -| arch | x86.disassemblyLowercase | bool | True | Lowercase opcodes, operands, and registers (False for uppercase) | +|Plugin | Setting | Type | Default | Description | +|----------:|-------------------------:|-------------:|---------------------------------------------------:|:----------------------------------------------------------------------------------------------------| +| analysis | autorunLinearSweep | boolean | True | Automatically run linear sweep when opening a binary for analysis | +| analysis | enabledUnicodeBlocks | list(string) | [] | Defines which Unicode blocks to consider when searching for strings | +| analysis | enableUTF8 | boolean | True | Whether or not to consider UTF-8 code points when searching for strings | +| analysis | enableUTF16 | boolean | True | Whether or not to consider UTF-16 code points when searching for strings | +| analysis | enableUTF32 | boolean | True | Whether or not to consider UTF-32 code points when searching for strings | +| analysis | max-function-size | integer | 65536 | Any functions over this size will not be automatically analyzed and require manual override | +| core | linux\_ca\_bundle | string | "" | Certificate authority (.pem or .crt) file to be used for secure downloads | +| core | linux\_ca\_dir | string | "" | Certificate authority directory (for distributions without a CA bundle) | +| ui | activeContent | boolean | True | Allow Binary Ninja to connect to the web to check for updates | +| ui | colorblind | boolean | True | Choose colors that are visible to those with red/green colorblind | +| ui | debug | boolean | False | Enable developer debugging features (Additional views: Lifted IL, and SSA forms) | +| ui | recent-file-limit | integer | 10 | Specify limit for number of recent files | +| ui | scriptingProvider | string | "Python" | Specify the registered ScriptingProvider that controls the 'Console' in the UI | +| pdb | local-store-absolute | string | "" | Absolute path specifying where the pdb symbol store exists on this machine, overrides relative path | +| pdb | local-store-relative | string | "symbols" | Path *relative* to the binaryninja _user_ directory, sepcifying the pdb symbol store | +| pdb | auto-download-pdb | boolean | True | Automatically download pdb files from specified symbol servers | +| pdb | symbol-server-list | list(string) | ["http://msdl.microsoft.com/download/symbols"] | List of servers to query for pdb symbols. | +| python | interpreter | string | "{/path/,C:\\\\Path\\\\}python27.{dylib,dll,so.1}" | Python interpreter to load if one is not already present when plugins are loaded | +| arch | x86.disassemblyFlavor | string | "BN_INTEL" | "BN_INTEL", "INTEL", or "AT&T" | +| arch | x86.disassemblySeperator | string | ", " | What to put between operands in disassembly tokens | +| arch | x86.disassemblyLowercase | bool | True | Lowercase opcodes, operands, and registers (False for uppercase) | Below is an example `settings.json` setting various options: ``` -- cgit v1.3.1 From 48e3e89061d20f1e198a5a705b841def4199bc01 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Mon, 24 Sep 2018 20:41:33 -0400 Subject: fix url in documentation --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 1a02a3a0..d47f7a71 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -55,7 +55,7 @@ You can load files in many ways: 3. Clicking an item in the recent files list 4. Running Binary Ninja with an optional command-line parameter 5. Opening a file from a URL via the `⌘-l` or `⌃-l` hotkey -6. Opening a file using the binaryninja: url handler. For security reasons, the url handler requires you to confirm a warning before opening a file via the url handler. The url handler can open remote URLs like: `binaryninja:https://captf.com/2015/plaidctf/pwnable/datastore_7e64104f876f0aa3f8330a409d9b9924.elf`, or even local files like `binarynina://bin/ls` in cases where you wish to script up Binary Ninja from a local webapp. +6. Opening a file using the binaryninja: url handler. For security reasons, the url handler requires you to confirm a warning before opening a file via the url handler. The url handler can open remote URLs like: `binaryninja:https://captf2.captf.com/2015/plaidctf/pwnable/datastore_7e64104f876f0aa3f8330a409d9b9924.elf`, or even local files like `binarynina://bin/ls` in cases where you wish to script up Binary Ninja from a local webapp. ![recent files](/img/recent.png "Recent Files") -- cgit v1.3.1 From ae83416100674f381b868c2c8bf2ac600613565d Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Tue, 25 Sep 2018 19:04:35 -0400 Subject: api: remove some lingering xrange uses in python bindings --- python/binaryview.py | 2 +- python/downloadprovider.py | 5 +++-- python/interaction.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/python/binaryview.py b/python/binaryview.py index 5786a5c8..efffe3e3 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -983,7 +983,7 @@ class BinaryView(object): info_ref = core.BNGetAnalysisInfo(self.handle) info = info_ref[0] active_info_list = [] - for i in xrange(0, info.count): + for i in range(0, info.count): func = binaryninja.function.Function(self, core.BNNewFunctionReference(info.activeInfo[i].func)) active_info = ActiveAnalysisInfo(func, info.activeInfo[i].analysisTime, info.activeInfo[i].updateCount, info.activeInfo[i].submitCount) active_info_list.append(active_info) diff --git a/python/downloadprovider.py b/python/downloadprovider.py index 1c94aef3..eb9735f3 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -35,6 +35,7 @@ from binaryninja import log # 2-3 compatibility from binaryninja import pyNativeStr +from binaryninja import range class DownloadInstance(object): @@ -85,7 +86,7 @@ class _DownloadProviderMetaclass(type): count = ctypes.c_ulonglong() types = core.BNGetDownloadProviderList(count) result = [] - for i in xrange(0, count.value): + for i in range(0, count.value): result.append(DownloadProvider(types[i])) core.BNFreeDownloadProviderList(types) return result @@ -95,7 +96,7 @@ class _DownloadProviderMetaclass(type): count = ctypes.c_ulonglong() types = core.BNGetDownloadProviderList(count) try: - for i in xrange(0, count.value): + for i in range(0, count.value): yield DownloadProvider(types[i]) finally: core.BNFreeDownloadProviderList(types) diff --git a/python/interaction.py b/python/interaction.py index e53312cf..1a4d37d5 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -587,7 +587,7 @@ class ReportCollection(object): def __iter__(self): count = len(self) - for i in xrange(0, count): + for i in range(0, count): yield self._report_from_index(i) def __repr__(self): -- cgit v1.3.1 From 474af687134930f8c1e8f13c6cfe5fd5574cf8c3 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Tue, 25 Sep 2018 20:17:16 -0400 Subject: add missing newline to fix formatting of code block for nix derivation file --- docs/guide/troubleshooting.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index 0aafd4a7..cea339a3 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -118,7 +118,8 @@ stdenv.mkDerivation rec { $out/bin/binaryninja \ --prefix "QT_XKB_CONFIG_ROOT" ":" "${xkeyboard_config}/share/X11/xkb" ''; -}``` +} +``` ## API -- cgit v1.3.1 From bd0215667a465dc23b4d0949fd8457d7e6411fd7 Mon Sep 17 00:00:00 2001 From: Ryan Snyder Date: Wed, 26 Sep 2018 07:11:37 -0400 Subject: python3: ensure callbacks live long enough --- python/binaryview.py | 14 +++++++++++++- python/downloadprovider.py | 8 ++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/python/binaryview.py b/python/binaryview.py index efffe3e3..9fea3fa7 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -604,8 +604,10 @@ class BinaryView(object): registered_view_type = None next_address = 0 _associated_data = {} + _registered_instances = [] def __init__(self, file_metadata=None, parent_view=None, handle=None): + self._must_free = True if handle is not None: self.handle = core.handle_of_type(handle, core.BNBinaryView) if file_metadata is None: @@ -625,6 +627,7 @@ class BinaryView(object): self._cb = core.BNCustomBinaryView() self._cb.context = 0 self._cb.init = self._cb.init.__class__(self._init) + self._cb.freeObject = self._cb.freeObject.__class__(self._free_object) self._cb.read = self._cb.read.__class__(self._read) self._cb.write = self._cb.write.__class__(self._write) self._cb.insert = self._cb.insert.__class__(self._insert) @@ -647,6 +650,8 @@ class BinaryView(object): if parent_view is not None: parent_view = parent_view.handle self.handle = core.BNCreateCustomBinaryView(self.__class__.name, file_metadata.handle, parent_view, self._cb) + self.__class__._registered_instances.append(self) + self._must_free = False self.notifications = {} self.next_address = None # Do NOT try to access view before init() is called, use placeholder @@ -792,7 +797,8 @@ class BinaryView(object): def __del__(self): for i in self.notifications.values(): i._unregister() - core.BNFreeBinaryView(self.handle) + if self._must_free: + core.BNFreeBinaryView(self.handle) def __iter__(self): count = ctypes.c_ulonglong(0) @@ -1190,6 +1196,12 @@ class BinaryView(object): log.log_error(traceback.format_exc()) return False + def _free_object(self, ctxt): + try: + self.__class__._registered_instances.remove(self) + except: + log.log_error(traceback.format_exc()) + def _read(self, ctxt, dest, offset, length): try: data = self.perform_read(offset, length) diff --git a/python/downloadprovider.py b/python/downloadprovider.py index eb9735f3..10f9dee1 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -39,6 +39,7 @@ from binaryninja import range class DownloadInstance(object): + _registered_instances = [] def __init__(self, provider, handle = None): if handle is None: self._cb = core.BNDownloadInstanceCallbacks() @@ -46,15 +47,18 @@ class DownloadInstance(object): self._cb.destroyInstance = self._cb.destroyInstance.__class__(self._destroy_instance) self._cb.performRequest = self._cb.performRequest.__class__(self._perform_request) self.handle = core.BNInitDownloadInstance(provider.handle, self._cb) + self.__class__._registered_instances.append(self) else: self.handle = core.handle_of_type(handle, core.BNDownloadInstance) - self._outputCallbacks = None + self._must_free = handle is not None def __del__(self): - core.BNFreeDownloadInstance(self.handle) + if self._must_free: + core.BNFreeDownloadInstance(self.handle) def _destroy_instance(self, ctxt): try: + self.__class__._registered_instances.remove(self) self.perform_destroy_instance() except: log.log_error(traceback.format_exc()) -- cgit v1.3.1 From 208333f896ba722072245c89d6bf2c919602b74b Mon Sep 17 00:00:00 2001 From: negasora Date: Mon, 1 Oct 2018 12:09:30 -0400 Subject: Patch FlowGraph locking --- python/flowgraph.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/python/flowgraph.py b/python/flowgraph.py index 4d551c83..79351838 100644 --- a/python/flowgraph.py +++ b/python/flowgraph.py @@ -554,8 +554,6 @@ class FlowGraph(object): return FlowGraphLayoutRequest(self, callback) def _wait_complete(self): - self._wait_cond.acquire() - self._wait_cond.notify() self._wait_cond.release() def layout_and_wait(self): @@ -566,12 +564,13 @@ class FlowGraph(object): Do not use this API on the UI thread (use ``layout`` with a callback instead). """ - self._wait_cond = threading.Condition() + self._wait_cond = threading.Lock() + + self._wait_cond.acquire() + request = self.layout(self._wait_complete) self._wait_cond.acquire() - while not request.complete: - self._wait_cond.wait() self._wait_cond.release() def get_nodes_in_region(self, left, top, right, bottom): -- cgit v1.3.1 From 49bf39278c0f614137863aa62aab600bb4d258cf Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Thu, 4 Oct 2018 17:41:45 -0400 Subject: Force use of requests on Windows to avoid cert issues on some installs --- python/downloadprovider.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/downloadprovider.py b/python/downloadprovider.py index 10f9dee1..b619b40c 100644 --- a/python/downloadprovider.py +++ b/python/downloadprovider.py @@ -153,7 +153,7 @@ class DownloadProvider(with_metaclass(_DownloadProviderMetaclass, object)): return DownloadInstance(self, handle = result) -if sys.version_info >= (2, 7, 9): +if (sys.platform != "win32") and (sys.version_info >= (2, 7, 9)): try: from urllib.request import urlopen, build_opener, install_opener, ProxyHandler from urllib.error import URLError @@ -216,7 +216,8 @@ if sys.version_info >= (2, 7, 9): else: try: import requests - from requests import pyopenssl + if sys.platform != "win32": + from requests import pyopenssl class PythonDownloadInstance(DownloadInstance): def __init__(self, provider): super(PythonDownloadInstance, self).__init__(provider) @@ -268,7 +269,9 @@ else: PythonDownloadProvider().register() except ImportError: if sys.platform == "win32": - log.log_error("Provided Python version is too old! Only Python 2.7.10 and above are known to work on Windows!") + log.log_error("The pip requests package is required for network connectivity!") + log.log_error("Please install the requests package into the selected Python environment:") + log.log_error(" python -m pip install requests") else: log.log_error("On Python versions below 2.7.9, the pip requests[security] package is required for network connectivity!") log.log_error("On an Ubuntu 14.04 install, the following three commands are sufficient to enable networking for the current user:") -- cgit v1.3.1 From 329eda77b5c91e7a74b4b209ca2f1cd06782061b Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Fri, 5 Oct 2018 10:23:55 -0400 Subject: add windows troubleshooting steps --- docs/guide/troubleshooting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index cea339a3..e53fc0b5 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -36,6 +36,12 @@ Next, if running a python plugin, make sure the python requirements are met by y - If experiencing problems with Windows UAC permissions during an update, the easiest fix is to completely un-install and [recover][recover] the latest installer and license. Preferences are saved outside the installation folder and are preserved, though you might want to remove your [license](/getting-started/#license). - If you need to change the email address on your license, contact [support]. +## Windows + +- While Windows 7 is not officially supported (by us, or Microsoft for that matter), it's possible to have Binary Ninja work if all available windows updates are installed as a library pack update somewhere in the updates is required for us to run. +- If you install Windows without internet access and have never run windows updates to install an update, you may have an incomplete windows certificate store. You'll see errors when attempting to update about `CERTIFICATE VERIFICATION FAILED`. If that is the case, you can either use something like `certutil.exe -generateSSTFromWU roots.sst` and then manually copy over the DST and Amazon certificates into your root store, or wait until the next time you have an update from Windows Update which should automatically refresh your certificate store. + + ## OS X While OS X is generally the most trouble-free environment for Binary Ninja, very old versions may have problems with the RPATH for our binaries and libraries. There are two solutions. First, run Binary Ninja with: -- cgit v1.3.1 From 24ac625877968df34d7863d3b8c6fda3ec7b8ce4 Mon Sep 17 00:00:00 2001 From: Jordan Wiens Date: Fri, 5 Oct 2018 10:24:30 -0400 Subject: remove duplicate hotkey entry --- docs/getting-started.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index d47f7a71..f8d88d97 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -100,7 +100,6 @@ Switching views happens multiple ways. In some instances, it's automatic (clicki - `y` : Change type - `a` : Change the data type to an ASCII string - [1248] : Change type directly to a data variable of the indicated widths - - `a` : Change the data type to an ASCII string - `d` : Switches between data variables of various widths - `r` : Change the data type to single ASCII character - `o` : Create a pointer data type -- cgit v1.3.1