mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch 'origin/patch'
This commit is contained in:
commit
e83e7004a3
7 changed files with 138 additions and 71 deletions
|
@ -30,11 +30,11 @@ This guide includes instructions for obtaining many of these at the relevant ste
|
||||||
You may not need all of these, depending on which portions you are building or developing.
|
You may not need all of these, depending on which portions you are building or developing.
|
||||||
#### At minimum you will need all of the following
|
#### At minimum you will need all of the following
|
||||||
* Java JDK 11 (64-bit) - Free long term support (LTS) versions of JDK 11 are provided by:
|
* Java JDK 11 (64-bit) - Free long term support (LTS) versions of JDK 11 are provided by:
|
||||||
- AdoptOpenJDK
|
- Adoptium Temurin
|
||||||
- https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot
|
- https://adoptium.net/releases.html?variant=openjdk11&jvmVariant=hotspot
|
||||||
- Amazon Corretto
|
- Amazon Corretto
|
||||||
- https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html
|
- https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html
|
||||||
* Gradle 6.0 or later - We use version 6.9, and tested with up to 7.0.1.
|
* Gradle 6 or 7
|
||||||
- https://gradle.org/releases/
|
- https://gradle.org/releases/
|
||||||
* A C/C++ compiler - We use GCC on Linux, Xcode (Clang) on macOS, and Visual Studio (2017 or later) on Windows.
|
* A C/C++ compiler - We use GCC on Linux, Xcode (Clang) on macOS, and Visual Studio (2017 or later) on Windows.
|
||||||
- https://gcc.gnu.org/
|
- https://gcc.gnu.org/
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ghidra.program.model.symbol.RefType;
|
||||||
import ghidra.program.model.symbol.SourceType;
|
import ghidra.program.model.symbol.SourceType;
|
||||||
import ghidra.trace.database.data.DBTraceDataSettingsOperations;
|
import ghidra.trace.database.data.DBTraceDataSettingsOperations;
|
||||||
import ghidra.trace.database.symbol.DBTraceReference;
|
import ghidra.trace.database.symbol.DBTraceReference;
|
||||||
|
import ghidra.trace.model.Trace.TraceCodeChangeType;
|
||||||
import ghidra.trace.model.listing.TraceData;
|
import ghidra.trace.model.listing.TraceData;
|
||||||
import ghidra.trace.model.symbol.TraceReference;
|
import ghidra.trace.model.symbol.TraceReference;
|
||||||
import ghidra.trace.util.*;
|
import ghidra.trace.util.*;
|
||||||
|
@ -38,150 +39,198 @@ public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, DataAdapterM
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default TraceReference[] getValueReferences() {
|
default TraceReference[] getValueReferences() {
|
||||||
return (TraceReference[]) DataAdapterMinimal.super.getValueReferences();
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
|
return (TraceReference[]) DataAdapterMinimal.super.getValueReferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void addValueReference(Address refAddr, RefType type) {
|
default void addValueReference(Address refAddr, RefType type) {
|
||||||
getTrace().getReferenceManager()
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
.addMemoryReference(getLifespan(), getAddress(), refAddr,
|
getTrace().getReferenceManager()
|
||||||
type, SourceType.USER_DEFINED, DATA_OP_INDEX);
|
.addMemoryReference(getLifespan(), getAddress(), refAddr,
|
||||||
|
type, SourceType.USER_DEFINED, DATA_OP_INDEX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void removeValueReference(Address refAddr) {
|
default void removeValueReference(Address refAddr) {
|
||||||
DBTraceReference ref = getTrace().getReferenceManager()
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
.getReference(getStartSnap(),
|
DBTraceReference ref = getTrace().getReferenceManager()
|
||||||
getAddress(), refAddr, DATA_OP_INDEX);
|
.getReference(getStartSnap(),
|
||||||
if (ref == null) {
|
getAddress(), refAddr, DATA_OP_INDEX);
|
||||||
return;
|
if (ref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ref.delete();
|
||||||
}
|
}
|
||||||
ref.delete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DBTraceDataSettingsOperations getSettingsSpace(boolean createIfAbsent);
|
DBTraceDataSettingsOperations getSettingsSpace(boolean createIfAbsent);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setLong(String name, long value) {
|
default void setLong(String name, long value) {
|
||||||
getSettingsSpace(true).setLong(getLifespan(), getAddress(), name, value);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
|
getSettingsSpace(true).setLong(getLifespan(), getAddress(), name, value);
|
||||||
|
}
|
||||||
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default Long getLong(String name) {
|
default Long getLong(String name) {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space != null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
Long value = space.getLong(getStartSnap(), getAddress(), name);
|
if (space != null) {
|
||||||
if (value != null) {
|
Long value = space.getLong(getStartSnap(), getAddress(), name);
|
||||||
return value;
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Settings defaultSettings = getDefaultSettings();
|
||||||
|
return defaultSettings == null ? null : defaultSettings.getLong(name);
|
||||||
}
|
}
|
||||||
Settings defaultSettings = getDefaultSettings();
|
|
||||||
return defaultSettings == null ? null : defaultSettings.getLong(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setString(String name, String value) {
|
default void setString(String name, String value) {
|
||||||
getSettingsSpace(true).setString(getLifespan(), getAddress(), name, value);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
|
getSettingsSpace(true).setString(getLifespan(), getAddress(), name, value);
|
||||||
|
}
|
||||||
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String getString(String name) {
|
default String getString(String name) {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space != null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
String value = space.getString(getStartSnap(), getAddress(), name);
|
if (space != null) {
|
||||||
if (value != null) {
|
String value = space.getString(getStartSnap(), getAddress(), name);
|
||||||
return value;
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Settings defaultSettings = getDefaultSettings();
|
||||||
|
return defaultSettings == null ? null : defaultSettings.getString(name);
|
||||||
}
|
}
|
||||||
Settings defaultSettings = getDefaultSettings();
|
|
||||||
return defaultSettings == null ? null : defaultSettings.getString(name);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setByteArray(String name, byte[] value) {
|
default void setByteArray(String name, byte[] value) {
|
||||||
getSettingsSpace(true).setBytes(getLifespan(), getAddress(), name, value);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
|
getSettingsSpace(true).setBytes(getLifespan(), getAddress(), name, value);
|
||||||
|
}
|
||||||
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default byte[] getByteArray(String name) {
|
default byte[] getByteArray(String name) {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space != null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
byte[] value = space.getBytes(getStartSnap(), getAddress(), name);
|
if (space != null) {
|
||||||
if (value != null) {
|
byte[] value = space.getBytes(getStartSnap(), getAddress(), name);
|
||||||
return value;
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Settings defaultSettings = getDefaultSettings();
|
||||||
|
return defaultSettings == null ? null : defaultSettings.getByteArray(name);
|
||||||
}
|
}
|
||||||
Settings defaultSettings = getDefaultSettings();
|
|
||||||
return defaultSettings == null ? null : defaultSettings.getByteArray(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void setValue(String name, Object value) {
|
default void setValue(String name, Object value) {
|
||||||
getSettingsSpace(true).setValue(getLifespan(), getAddress(), name, value);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
|
getSettingsSpace(true).setValue(getLifespan(), getAddress(), name, value);
|
||||||
|
}
|
||||||
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default Object getValue(String name) {
|
default Object getValue(String name) {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space != null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
Object value = space.getValue(getStartSnap(), getAddress(), name);
|
if (space != null) {
|
||||||
if (value != null) {
|
Object value = space.getValue(getStartSnap(), getAddress(), name);
|
||||||
return value;
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Settings defaultSettings = getDefaultSettings();
|
||||||
|
return defaultSettings == null ? null : defaultSettings.getValue(name);
|
||||||
}
|
}
|
||||||
Settings defaultSettings = getDefaultSettings();
|
|
||||||
return defaultSettings == null ? null : defaultSettings.getValue(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void clearSetting(String name) {
|
default void clearSetting(String name) {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
if (space == null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
return;
|
if (space == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
space.clear(getLifespan(), getAddress(), name);
|
||||||
}
|
}
|
||||||
space.clear(getLifespan(), getAddress(), name);
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void clearAllSettings() {
|
default void clearAllSettings() {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockWrite()) {
|
||||||
if (space == null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
return;
|
if (space == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
space.clear(getLifespan(), getAddress(), null);
|
||||||
}
|
}
|
||||||
space.clear(getLifespan(), getAddress(), null);
|
getTrace().setChanged(new TraceChangeRecord<>(
|
||||||
|
TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null,
|
||||||
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String[] getNames() {
|
default String[] getNames() {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space == null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
return EMPTY_STRING_ARRAY;
|
if (space == null) {
|
||||||
|
return EMPTY_STRING_ARRAY;
|
||||||
|
}
|
||||||
|
Collection<String> names = space.getSettingNames(getLifespan(), getAddress());
|
||||||
|
return names.toArray(new String[names.size()]);
|
||||||
}
|
}
|
||||||
Collection<String> names = space.getSettingNames(getLifespan(), getAddress());
|
|
||||||
return names.toArray(new String[names.size()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean isEmpty() {
|
default boolean isEmpty() {
|
||||||
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
if (space == null) {
|
DBTraceDataSettingsOperations space = getSettingsSpace(false);
|
||||||
return true;
|
if (space == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return space.isEmpty(getLifespan(), getAddress());
|
||||||
}
|
}
|
||||||
return space.isEmpty(getLifespan(), getAddress());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default <T extends SettingsDefinition> T getSettingsDefinition(
|
default <T extends SettingsDefinition> T getSettingsDefinition(
|
||||||
Class<T> settingsDefinitionClass) {
|
Class<T> settingsDefinitionClass) {
|
||||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
return DataAdapterFromSettings.super.getSettingsDefinition(settingsDefinitionClass);
|
return DataAdapterFromSettings.super.getSettingsDefinition(settingsDefinitionClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean hasMutability(int mutabilityType) {
|
default boolean hasMutability(int mutabilityType) {
|
||||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
try (LockHold hold = getTrace().lockRead()) {
|
||||||
return DataAdapterFromSettings.super.hasMutability(mutabilityType);
|
return DataAdapterFromSettings.super.hasMutability(mutabilityType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import ghidra.trace.model.listing.TraceInstruction;
|
||||||
import ghidra.trace.model.symbol.TraceReference;
|
import ghidra.trace.model.symbol.TraceReference;
|
||||||
import ghidra.trace.util.*;
|
import ghidra.trace.util.*;
|
||||||
import ghidra.util.LockHold;
|
import ghidra.util.LockHold;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.database.DBCachedObjectStore;
|
import ghidra.util.database.DBCachedObjectStore;
|
||||||
import ghidra.util.database.DBObjectColumn;
|
import ghidra.util.database.DBObjectColumn;
|
||||||
import ghidra.util.database.annot.*;
|
import ghidra.util.database.annot.*;
|
||||||
|
@ -139,8 +140,10 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
||||||
prototype = space.manager.getPrototypeByKey(prototypeKey);
|
prototype = space.manager.getPrototypeByKey(prototypeKey);
|
||||||
if (prototype == null) {
|
if (prototype == null) {
|
||||||
// TODO: Better to just load a sentinel? Why bail on the whole thing?
|
// TODO: Better to just load a sentinel? Why bail on the whole thing?
|
||||||
throw new IOException(
|
Msg.error(this,
|
||||||
"Instruction table is corrupt. Missing prototype: " + prototypeKey);
|
"Instruction table is corrupt for address " + getMinAddress() +
|
||||||
|
". Missing prototype " + prototypeKey);
|
||||||
|
prototype = new InvalidPrototype(getTrace().getBaseLanguage());
|
||||||
}
|
}
|
||||||
flowOverride = FlowOverride.values()[(flags & FLOWOVERRIDE_SET_MASK) >> FLOWOVERRIDE_SHIFT];
|
flowOverride = FlowOverride.values()[(flags & FLOWOVERRIDE_SET_MASK) >> FLOWOVERRIDE_SHIFT];
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,8 @@ public class DBTraceProgramView implements TraceProgramView {
|
||||||
listenFor(TraceCodeChangeType.REMOVED, this::codeRemoved);
|
listenFor(TraceCodeChangeType.REMOVED, this::codeRemoved);
|
||||||
listenFor(TraceCodeChangeType.FRAGMENT_CHANGED, this::codeFragmentChanged);
|
listenFor(TraceCodeChangeType.FRAGMENT_CHANGED, this::codeFragmentChanged);
|
||||||
listenFor(TraceCodeChangeType.DATA_TYPE_REPLACED, this::codeDataTypeReplaced);
|
listenFor(TraceCodeChangeType.DATA_TYPE_REPLACED, this::codeDataTypeReplaced);
|
||||||
|
listenFor(TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED,
|
||||||
|
this::codeDataTypeSettingsChanged);
|
||||||
|
|
||||||
listenFor(TraceCommentChangeType.EOL_CHANGED, this::commentEolChanged);
|
listenFor(TraceCommentChangeType.EOL_CHANGED, this::commentEolChanged);
|
||||||
listenFor(TraceCommentChangeType.PLATE_CHANGED, this::commentPlateChanged);
|
listenFor(TraceCommentChangeType.PLATE_CHANGED, this::commentPlateChanged);
|
||||||
|
@ -330,10 +332,21 @@ public class DBTraceProgramView implements TraceProgramView {
|
||||||
if (queues == null) {
|
if (queues == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_CODE_REPLACED,
|
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_REPLACED,
|
||||||
range.getX1(), range.getX2(), null, null, null));
|
range.getX1(), range.getX2(), null, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void codeDataTypeSettingsChanged(TraceAddressSpace space,
|
||||||
|
TraceAddressSnapRange range) {
|
||||||
|
DomainObjectEventQueues queues = isVisible(space, range);
|
||||||
|
if (queues == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Yes, x1 twice
|
||||||
|
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED,
|
||||||
|
range.getX1(), range.getX1(), null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
private void commentChanged(int docrType, TraceAddressSpace space,
|
private void commentChanged(int docrType, TraceAddressSpace space,
|
||||||
TraceAddressSnapRange range,
|
TraceAddressSnapRange range,
|
||||||
String oldValue, String newValue) {
|
String oldValue, String newValue) {
|
||||||
|
|
|
@ -106,6 +106,8 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
// Long is data type ID
|
// Long is data type ID
|
||||||
public static final TraceCodeChangeType<TraceAddressSnapRange, Long> DATA_TYPE_REPLACED =
|
public static final TraceCodeChangeType<TraceAddressSnapRange, Long> DATA_TYPE_REPLACED =
|
||||||
new TraceCodeChangeType<>();
|
new TraceCodeChangeType<>();
|
||||||
|
public static final TraceCodeChangeType<TraceAddressSnapRange, Void> DATA_TYPE_SETTINGS_CHANGED =
|
||||||
|
new TraceCodeChangeType<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class TraceCommentChangeType
|
public static final class TraceCommentChangeType
|
||||||
|
|
|
@ -86,8 +86,8 @@ Ghidra team if you have a specific need.</p></blockquote>
|
||||||
<li>Free long term support (LTS) versions of JDK 11 are provided by:</li>
|
<li>Free long term support (LTS) versions of JDK 11 are provided by:</li>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot">
|
<a href="https://adoptium.net/releases.html?variant=openjdk11&jvmVariant=hotspot">
|
||||||
AdoptOpenJDK</a>
|
Adoptium Temurin</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html">
|
<a href="https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html">
|
||||||
|
|
|
@ -38,7 +38,7 @@ To create the latest development build for your platform from this source reposi
|
||||||
|
|
||||||
##### Install build tools:
|
##### Install build tools:
|
||||||
* [JDK 11 64-bit][jdk11]
|
* [JDK 11 64-bit][jdk11]
|
||||||
* [Gradle][gradle] (minimum v6.0)
|
* [Gradle 6 or 7][gradle]
|
||||||
* make, gcc, and g++ (Linux/macOS-only)
|
* make, gcc, and g++ (Linux/macOS-only)
|
||||||
* [Microsoft Visual Studio][vs] (Windows-only)
|
* [Microsoft Visual Studio][vs] (Windows-only)
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ source project.
|
||||||
[devguide]: DevGuide.md
|
[devguide]: DevGuide.md
|
||||||
[career]: https://www.intelligencecareers.gov/nsa
|
[career]: https://www.intelligencecareers.gov/nsa
|
||||||
[project]: https://www.ghidra-sre.org/
|
[project]: https://www.ghidra-sre.org/
|
||||||
[jdk11]: https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot
|
[jdk11]: https://adoptium.net/releases.html?variant=openjdk11&jvmVariant=hotspot
|
||||||
[gradle]: https://gradle.org/releases/
|
[gradle]: https://gradle.org/releases/
|
||||||
[vs]: https://visualstudio.microsoft.com/vs/community/
|
[vs]: https://visualstudio.microsoft.com/vs/community/
|
||||||
[eclipse]: https://www.eclipse.org/downloads/packages/
|
[eclipse]: https://www.eclipse.org/downloads/packages/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue