You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by en...@apache.org on 2021/04/01 21:35:18 UTC

[netbeans] 02/03: Native debugging of GraalVM's native images.

This is an automated email from the ASF dual-hosted git repository.

entl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git

commit 0344bcf958a48aff2f941738a0f7711bb5d2f3f9
Author: Martin Entlicher <ma...@oracle.com>
AuthorDate: Mon Mar 22 16:44:49 2021 +0100

    Native debugging of GraalVM's native images.
---
 cpplite/cpplite.debugger/nbproject/project.xml     |  18 +
 .../modules/cpplite/debugger/CPPFrame.java         | 247 +++++++--
 .../modules/cpplite/debugger/CPPLiteDebugger.java  | 150 ++++--
 .../cpplite/debugger/CPPLiteDebuggerConfig.java    |  10 +-
 .../modules/cpplite/debugger/CPPThread.java        |  11 +-
 .../modules/cpplite/debugger/CPPVariable.java      |  61 ++-
 .../debugger/DebuggerBreakpointAnnotation.java     |  14 +-
 .../modules/cpplite/debugger/ThreadsCollector.java |   2 +-
 .../cpplite/debugger/ToolTipAnnotation.java        |  11 +-
 .../modules/cpplite/debugger/api/Debugger.java     |   6 +-
 .../breakpoints/BreakpointAnnotationProvider.java  |  13 +-
 .../debugger/breakpoints/BreakpointModel.java      |  15 +-
 .../debugger/breakpoints/BreakpointsReader.java    |  89 ++-
 .../debugger/breakpoints/CPPLiteBreakpoint.java    | 135 ++++-
 .../CPPLiteBreakpointActionProvider.java           |  33 +-
 .../debugger/breakpoints/PersistenceManager.java   |  13 +-
 .../debugger/debuggingview/DebuggingModel.java     |   4 +-
 .../cpplite/debugger/models/CallStackModel.java    |  27 +-
 .../cpplite/debugger/models/VariablesModel.java    |  52 +-
 .../cpplite/debugger/models/WatchesModel.java      |  27 +-
 .../modules/cpplite/debugger/ni/NIBreakpoints.java |  85 +++
 .../debugger/ni/NIDebuggerProviderImpl.java        | 172 ++++++
 .../NIDebuggerServiceProviderImpl.java}            |  25 +-
 .../cpplite/debugger/AbstractDebugTest.java        |   3 +-
 .../modules/cpplite/debugger/BreakpointsTest.java  |   8 +-
 .../modules/cpplite/debugger/StepTest.java         |   2 +-
 .../cpplite/project/ActionProviderImpl.java        |   2 +-
 ide/ide.kit/nbproject/project.xml                  |   7 +
 ide/nativeimage.api/build.xml                      |  28 +
 ide/nativeimage.api/manifest.mf                    |   6 +
 ide/nativeimage.api/nbproject/project.properties   |  19 +
 .../nativeimage.api}/nbproject/project.xml         | 100 +---
 .../modules/nativeimage/api/Bundle.properties      |  20 +
 .../nativeimage/api/debug}/EvaluateException.java  |  24 +-
 .../modules/nativeimage/api/debug/NIDebugger.java  | 211 ++++++++
 .../modules/nativeimage/api/debug/NIFrame.java     |  69 +++
 .../api/debug/NILineBreakpointDescriptor.java      | 179 +++++++
 .../modules/nativeimage/api/debug/NIVariable.java  |  96 ++++
 .../nativeimage/spi/debug/NIDebuggerProvider.java  | 123 +++++
 .../spi/debug/NIDebuggerServiceProvider.java       |  18 +-
 .../spi/debug/filters/FrameDisplayer.java          | 166 ++++++
 .../spi/debug/filters/VariableDisplayer.java       |  23 +-
 .../nativeimage/debug/NIDebuggerServiceTest.java   |  67 +++
 .../nativeimage/debug/TestNIDebuggerProvider.java  | 103 ++++
 .../debug/TestNIDebuggerServiceProvider.java       |  25 +-
 java/java.kit/nbproject/project.xml                |   7 +
 .../nbcode/nbproject/platform.properties           |   8 +-
 java/java.lsp.server/nbproject/project.xml         |  18 +
 java/java.lsp.server/script/etc/nbcode.clusters    |   1 +
 .../lsp/server/debugging/NbProtocolServer.java     | 115 ++--
 .../java/lsp/server/debugging/NbThreads.java       |  21 +-
 .../server/debugging/launch/NbDebugSession.java    |  27 +-
 .../server/debugging/launch/NbLaunchDelegate.java  | 219 +++++---
 .../debugging/launch/NbLaunchRequestHandler.java   |  64 ++-
 .../variables/NbVariablesRequestHandler.java       |  23 +-
 java/java.lsp.server/vscode/package.json           |  55 ++
 java/java.lsp.server/vscode/src/extension.ts       |  26 +
 java/java.nativeimage.debugger/build.xml           |  28 +
 java/java.nativeimage.debugger/manifest.mf         |   7 +
 .../nbproject/project.properties                   |  19 +
 .../nbproject/project.xml                          |  88 +--
 .../java/nativeimage/debugger/Bundle.properties    |  20 +
 .../nativeimage/debugger/actions/Bundle.properties |  21 +
 .../debugger/actions/NIAttachCustomizer.form       | 125 +++++
 .../debugger/actions/NIAttachCustomizer.java       | 378 +++++++++++++
 .../nativeimage/debugger/actions/NIAttachType.java |  56 ++
 .../nativeimage/debugger/api/NIDebugRunner.java    |  90 ++++
 .../breakpoints/JPDABreakpointsHandler.java        | 183 +++++++
 .../debugger/displayer/JavaFrameDisplayer.java     | 206 +++++++
 .../debugger/displayer/JavaVariablesDisplayer.java | 596 +++++++++++++++++++++
 .../nativeimage/debugger/resources/mf-layer.xml    |  32 ++
 .../nativeimage/debugger/NIDebugRunnerTest.java    |  78 +++
 .../debugger/TestNIDebuggerProvider.java           | 122 +++++
 .../debugger/TestNIDebuggerServiceProvider.java    |  18 +-
 .../java/nativeimage/debugger/TestNIVariable.java  |  71 +--
 nbbuild/build.properties                           |   1 +
 nbbuild/cluster.properties                         |   2 +
 nbbuild/javadoctools/links.xml                     |   1 +
 nbbuild/javadoctools/properties.xml                |   1 +
 nbbuild/javadoctools/replaces.xml                  |   1 +
 80 files changed, 4624 insertions(+), 623 deletions(-)

diff --git a/cpplite/cpplite.debugger/nbproject/project.xml b/cpplite/cpplite.debugger/nbproject/project.xml
index d360c06..48ff741 100644
--- a/cpplite/cpplite.debugger/nbproject/project.xml
+++ b/cpplite/cpplite.debugger/nbproject/project.xml
@@ -52,6 +52,24 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.extexecution</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>1.59</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.nativeimage.api</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.projectapi</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPFrame.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPFrame.java
index 3e5fd4c..c7315f5 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPFrame.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPFrame.java
@@ -19,14 +19,16 @@
 package org.netbeans.modules.cpplite.debugger;
 
 import java.io.File;
+import java.net.MalformedURLException;
 import java.net.URI;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
-import java.util.function.Consumer;
+import java.util.concurrent.CompletableFuture;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+
 import org.netbeans.api.annotations.common.CheckForNull;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIRecord;
@@ -34,10 +36,17 @@ import org.netbeans.modules.cnd.debugger.gdb2.mi.MIResult;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MITList;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MITListItem;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue;
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer.DisplayedFrame;
 import org.netbeans.spi.debugger.ui.DebuggingView.DVFrame;
+
 import org.openide.cookies.LineCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
 import org.openide.text.Line;
 import org.openide.util.Pair;
 
@@ -46,47 +55,62 @@ public final class CPPFrame implements DVFrame {
     private static final Logger LOGGER = Logger.getLogger(CPPFrame.class.getName());
 
     private final CPPThread thread;
-    private final String address;
-    public final String shortFileName;
-    public final String fullFileName;
-    public final String functionName;
-    public final int line;
+    private final DisplayedFrame displayedFrame;
+    private final NIFrame niFrame;
     public final int level;
 
-    private volatile Map<String, CPPVariable> variables;
+    private volatile Map<String, NIVariable> variables;
 
-    CPPFrame(CPPThread thread, MITList frame) {
+    private CPPFrame(CPPThread thread, DisplayedFrame displayedFrame, NIFrame niFrame) {
         Objects.requireNonNull(thread);
+        Objects.requireNonNull(displayedFrame);
+        Objects.requireNonNull(niFrame);
         this.thread = thread;
-        this.address = frame.getConstValue("addr");
-        this.shortFileName = frame.valueOf("file") != null ? frame.valueOf("file").asConst().value() : null;
-        this.functionName = frame.valueOf("func").asConst().value();
-        this.fullFileName = frame.valueOf("fullname") != null ? frame.valueOf("fullname").asConst().value() : null;
-        this.line = frame.valueOf("line") != null ? Integer.parseInt(frame.valueOf("line").asConst().value()) : 1;
-        if (frame.valueOf("level") != null) {
-            this.level = Integer.parseInt(frame.valueOf("level").asConst().value());
-        } else {
-            this.level = 0;
+        this.displayedFrame = displayedFrame;
+        this.niFrame = niFrame;
+        this.level = niFrame.getLevel();
+    }
+
+    static CPPFrame create(CPPThread thread, MITList frame) {
+        NIFrame niFrame = new NIFrameImpl(thread.getId(), frame);
+        FrameDisplayer frameDisplayer = thread.getDebugger().getContextProvider().lookupFirst(null, FrameDisplayer.class);
+        DisplayedFrame displayedFrame = frameDisplayer != null ? frameDisplayer.displayed(niFrame) : createDisplayedFrame(niFrame);
+        if (displayedFrame == null) {
+            return null; // Not to be displayed
         }
+        return new CPPFrame(thread, displayedFrame, niFrame);
     }
 
-    String getAddress() {
-        return address;
+    NIFrame getFrame() {
+        return niFrame;
     }
 
     @Override
     public String getName() {
-        return functionName;
+        return displayedFrame.getDisplayName();
+    }
+
+    public String getDescription() {
+        return displayedFrame.getDescription();
     }
 
     @CheckForNull
     public Line location() {
-        FileObject file = FileUtil.toFileObject(FileUtil.normalizeFile(new File(fullFileName)));
+        URI sourceURI = displayedFrame.getSourceURI();
+        if (sourceURI == null) {
+            return null;
+        }
+        FileObject file;
+        try {
+            file = URLMapper.findFileObject(sourceURI.toURL());
+        } catch (MalformedURLException ex) {
+            return null;
+        }
         if (file == null) {
             return null;
         }
         LineCookie lc = file.getLookup().lookup(LineCookie.class);
-        return lc.getLineSet().getOriginal(line - 1);
+        return lc.getLineSet().getOriginal(displayedFrame.getLine() - 1);
     }
 
     @Override
@@ -101,17 +125,12 @@ public final class CPPFrame implements DVFrame {
 
     @Override
     public URI getSourceURI() {
-        FileObject file = FileUtil.toFileObject(FileUtil.normalizeFile(new File(fullFileName)));
-        if (file != null) {
-            return file.toURI();
-        } else {
-            return null;
-        }
+        return displayedFrame.getSourceURI();
     }
 
     @Override
     public int getLine() {
-        return line;
+        return displayedFrame.getLine();
     }
 
     @Override
@@ -119,8 +138,8 @@ public final class CPPFrame implements DVFrame {
         return -1;
     }
 
-    public Map<String, CPPVariable> getVariables() {
-        Map<String, CPPVariable> vars = variables;
+    public Map<String, NIVariable> getVariables() {
+        Map<String, NIVariable> vars = variables;
         if (vars == null) {
             synchronized (this) {
                 vars = variables;
@@ -132,12 +151,13 @@ public final class CPPFrame implements DVFrame {
         return vars;
     }
 
-    static Map<String, CPPVariable> retrieveVariables(CPPFrame frame, CPPVariable parentVar) {
+    static Map<String, NIVariable> retrieveVariables(CPPFrame frame, CPPVariable parentVar) {
         MIRecord record;
         try {
             if (parentVar == null) {
                 record = frame.thread.getDebugger().sendAndGet("-stack-list-variables --thread " + frame.thread.getId() + " --frame " + frame.level + " --no-frame-filters 2");
             } else {
+                // from to
                 record = frame.thread.getDebugger().sendAndGet("-var-list-children --thread " + frame.thread.getId() + " --frame " + frame.level + " --all-values " + parentVar.getUniqueName());
             }
         } catch (InterruptedException ex) {
@@ -150,7 +170,7 @@ public final class CPPFrame implements DVFrame {
         if (results.isEmpty()) {
             return Collections.emptyMap();
         }
-        Map<String, CPPVariable> map = new LinkedHashMap<>(results.size());
+        Map<String, NIVariable> map = new LinkedHashMap<>(results.size());
         MIValue children = results.valueOf("children");
         if (children != null) {
             for (MITListItem item : children.asList()) {
@@ -160,11 +180,13 @@ public final class CPPFrame implements DVFrame {
                 int numChildren = Integer.parseInt(child.getConstValue("numchild"));
                 String type = child.getConstValue("type");
                 MIValue value = child.valueOf("value");
-                map.put(name, new CPPVariable(frame, uniqueName, name, type, value, numChildren));
+                map.put(name, new CPPVariable(frame, parentVar, uniqueName, name, type, value, numChildren));
             }
         } else {
             MIValue resultValue = results.valueOf("variables");
-            if (resultValue.isConst()) {
+            if (resultValue == null) {
+                return Collections.emptyMap();
+            } else if (resultValue.isConst()) {
                 return Collections.singletonMap(((MIConst) resultValue).value(), null);
             }
             results = (MITList) resultValue;
@@ -176,9 +198,11 @@ public final class CPPFrame implements DVFrame {
                 MIValue value = varList.valueOf("value");
                 Pair<String, Integer> uniqueVar = createVariable(frame, name);
                 String uniqueName = uniqueVar.first();
-                int numChildren = uniqueVar.second();
-                LOGGER.log(Level.FINE, "  {0} = ({1}) {2} ; [{3}]", new Object[]{name, type, value, numChildren});
-                map.put(name, new CPPVariable(frame, uniqueName, name, type, value, numChildren));
+                if (uniqueName != null) {
+                    int numChildren = uniqueVar.second();
+                    LOGGER.log(Level.FINE, "  {0} = ({1}) {2} ; [{3}]", new Object[]{name, type, value, numChildren});
+                    map.put(name, new CPPVariable(frame, parentVar, uniqueName, name, type, value, numChildren));
+                }
             }
         }
         return map;
@@ -191,9 +215,12 @@ public final class CPPFrame implements DVFrame {
         try {
             record = frame.thread.getDebugger().sendAndGet("-var-create --thread " + frame.thread.getId() + " --frame " + frame.level + " - " + "*" + " " + variableName);
             if (!record.isError() && !record.isEmpty()) {
-                uniqueName = record.results().getConstValue("name");
-                String numchild = record.results().getConstValue("numchild");
-                numChildren = Integer.parseInt(numchild);
+                String name = record.results().getConstValue("name");
+                if (!name.isEmpty()) {
+                    uniqueName = name;
+                    String numchild = record.results().getConstValue("numchild");
+                    numChildren = Integer.parseInt(numchild);
+                }
             }
         } catch (InterruptedException ex) {
         }
@@ -216,13 +243,21 @@ public final class CPPFrame implements DVFrame {
         return numChildren;
     }
 
-    public void evaluateLazy(String expression, Consumer<CPPVariable> result, Consumer<EvaluateException> exception) {
-        CPPVariable value = getVariables().get(expression);
+    private static final String MI_ERROR = "MI parse error: ";
+
+    public CompletableFuture<NIVariable> evaluateAsync(String expression) {
+        return evaluateAsync(expression, null);
+    }
+
+    public CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName) {
+        String resultVarName = (resultName != null) ? resultName : expression;
+        CompletableFuture<NIVariable> result = new CompletableFuture<>();
+        NIVariable value = getVariables().get(expression);
         if (value != null) {
-            result.accept(value);
-            return;
+            result.complete(value);
+            return result;
         }
-        thread.getDebugger().send(new Command("-var-create - * " + expression) {
+        thread.getDebugger().send(new Command("-var-create --thread " + thread.getId() + " --frame " + level + " - * " + expression) {
             @Override
             protected void onDone(MIRecord record) {
                 MITList results = record.results();
@@ -237,14 +272,128 @@ public final class CPPFrame implements DVFrame {
                 } else {
                     numChildren = retrieveNumChildren(CPPFrame.this, varName);
                 }
-                result.accept(new CPPVariable(CPPFrame.this, expression, expression, type, resultValue, numChildren));
-                thread.getDebugger().send(new Command("-var-delete " + varName));
+                result.complete(new CPPVariable(CPPFrame.this, null, varName, resultVarName, type, resultValue, numChildren));
+                //thread.getDebugger().send(new Command("-var-delete " + varName));
             }
             @Override
             protected void onError(MIRecord record) {
-                exception.accept(new EvaluateException(record.error()));
+                String error = record.error();
+                if (error.startsWith(MI_ERROR)) {
+                    error = error.substring(MI_ERROR.length());
+                    result.completeExceptionally(new EvaluateException(error));
+                }
             }
         });
+        return result;
+    }
+
+    private static DisplayedFrame createDisplayedFrame(NIFrame frame) {
+        return DisplayedFrame.newBuilder(getDisplayName(frame))
+                .description(getDescription(frame))
+                .line(frame.getLine())
+                .sourceURISupplier(() -> getSourceURI(frame))
+                .build();
+    }
+
+    private static String getDisplayName(NIFrame frame) {
+        StringBuilder builder = new StringBuilder(frame.getFunctionName());
+        String shortName = frame.getShortFileName();
+        if (shortName != null) {
+            builder.append("; ");
+            builder.append(shortName);
+        }
+        int line = frame.getLine();
+        if (line > 0) {
+            builder.append(':');
+            builder.append(line);
+        }
+        return builder.toString();
+    }
+
+    private static String getDescription(NIFrame frame) {
+        StringBuilder builder = new StringBuilder(frame.getFunctionName());
+        String fullName = frame.getFullFileName();
+        if (fullName != null) {
+            builder.append("; ");
+            builder.append(fullName);
+        }
+        int line = frame.getLine();
+        if (line > 0) {
+            builder.append(':');
+            builder.append(line);
+        }
+        return builder.toString();
+    }
 
+    private static URI getSourceURI(NIFrame frame) {
+        String fullFileName = frame.getFullFileName();
+        if (fullFileName != null && !fullFileName.isEmpty()) {
+            FileObject file = FileUtil.toFileObject(FileUtil.normalizeFile(new File(fullFileName)));
+            if (file != null) {
+                return file.toURI();
+            }
+        }
+        return null;
     }
+
+    private static final class NIFrameImpl implements NIFrame {
+
+        private final String threadId;
+        private final int level;
+        private final String address;
+        private final String shortFileName;
+        private final String fullFileName;
+        private final String functionName;
+        private final int line;
+
+        NIFrameImpl(String threadId, MITList frame) {
+            this.threadId = threadId;
+            this.address = frame.getConstValue("addr");
+            this.shortFileName = frame.valueOf("file") != null ? frame.valueOf("file").asConst().value() : null;
+            this.functionName = frame.valueOf("func").asConst().value();
+            this.fullFileName = frame.valueOf("fullname") != null ? frame.valueOf("fullname").asConst().value() : null;
+            this.line = frame.valueOf("line") != null ? Integer.parseInt(frame.valueOf("line").asConst().value()) : -1;
+            if (frame.valueOf("level") != null) {
+                this.level = Integer.parseInt(frame.valueOf("level").asConst().value());
+            } else {
+                this.level = 0;
+            }
+        }
+
+        @Override
+        public String getAddress() {
+            return address;
+        }
+
+        @Override
+        public String getShortFileName() {
+            return shortFileName;
+        }
+
+        @Override
+        public String getFullFileName() {
+            return fullFileName;
+        }
+
+        @Override
+        public String getFunctionName() {
+            return functionName;
+        }
+
+        @Override
+        public int getLine() {
+            return line;
+        }
+
+        @Override
+        public String getThreadId() {
+            return threadId;
+        }
+
+        @Override
+        public int getLevel() {
+            return level;
+        }
+    }
+
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
index 87d6587..6ca0f17 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.EventListener;
 import java.util.List;
 import java.util.Map;
@@ -51,6 +52,7 @@ import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIProxy;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIRecord;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MITList;
+import org.netbeans.modules.cnd.debugger.gdb2.mi.MITListItem;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue;
 import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
 import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory;
@@ -125,6 +127,10 @@ public final class CPPLiteDebugger {
         proxy.send(new Command("-gdb-set target-async"));
         //proxy.send(new Command("-gdb-set scheduler-locking on"));
         proxy.send(new Command("-gdb-set non-stop on"));
+        proxy.send(new Command("-gdb-set print object on"));
+    }
+
+    public void execRun() {
         proxy.send(new Command("-exec-run"));
     }
 
@@ -365,6 +371,46 @@ public final class CPPLiteDebugger {
         LOGGER.fine("finish() done, build finished.");
     }
 
+    public String readMemory(String address, long offset, int length) {
+        MIRecord memory;
+        String offsetArg;
+        if (offset != 0) {
+            offsetArg = "-o " + offset + " ";
+        } else {
+            offsetArg = "";
+        }
+        try {
+            memory = sendAndGet("-data-read-memory-bytes " + offsetArg + address + " " + length);
+        } catch (InterruptedException ex) {
+            return null;
+        }
+        MIValue memoryValue = memory.results().valueOf("memory");
+        if (memoryValue instanceof MITList) {
+            MITList memoryList = (MITList) memoryValue;
+            if (!memoryList.isEmpty()) {
+                MITListItem row = memoryList.get(0);
+                if (row instanceof MITList) {
+                    String contents = ((MITList) row).getConstValue("contents");
+                    return contents;
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getVersion() {
+        MIRecord versionRecord;
+        try {
+            versionRecord = sendAndGet("-gdb-version");
+        } catch (InterruptedException ex) {
+            return null;
+        }
+        return versionRecord.results().toString();
+    }
+
+    ContextProvider getContextProvider() {
+        return contextProvider;
+    }
 
     DebuggingView.DVSupport getDVSupport() {
         return contextProvider.lookupFirst(null, DebuggingView.DVSupport.class);
@@ -421,7 +467,7 @@ public final class CPPLiteDebugger {
                                 break;
                             default:
                                 MITList topFrameList = (MITList) results.valueOf("frame");
-                                CPPFrame frame = topFrameList != null ? new CPPFrame(thread, topFrameList) : null;
+                                CPPFrame frame = topFrameList != null ? CPPFrame.create(thread, topFrameList) : null;
                                 thread.setTopFrame(frame);
                                 setSuspended(true, thread, frame);
                                 if (frame != null) {
@@ -497,8 +543,16 @@ public final class CPPLiteDebugger {
         void send(MICommand cmd, boolean waitForRunning) {
             if (waitForRunning) {
                 waitRunning();
+                send(cmd);
+            } else {
+                try {
+                    startedLatch.await();
+                } catch (InterruptedException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+                LOGGER.log(Level.FINE, "MIProxy.send({0})", cmd);
+                super.send(cmd);
             }
-            send(cmd);
         }
 
         @Override
@@ -548,40 +602,41 @@ public final class CPPLiteDebugger {
 
     }
 
-    public static @NonNull Pair<DebuggerEngine, Process> startDebugging (CPPLiteDebuggerConfig configuration) throws IOException {
-        DebuggerInfo di = DebuggerInfo.create (
-            "CPPLiteDebuggerInfo",
-            new Object[] {
-                new SessionProvider () {
-                    @Override
-                    public String getSessionName () {
-                        return configuration.getDisplayName ();
-                    }
-
-                    @Override
-                    public String getLocationName () {
-                        return "localhost";
-                    }
+    public static @NonNull Pair<DebuggerEngine, Process> startDebugging (CPPLiteDebuggerConfig configuration, Object... services) throws IOException {
+        SessionProvider sessionProvider = new SessionProvider () {
+            @Override
+            public String getSessionName () {
+                return configuration.getDisplayName ();
+            }
 
-                    @Override
-                    public String getTypeID () {
-                        return "CPPLiteSession";
-                    }
+            @Override
+            public String getLocationName () {
+                return "localhost";
+            }
 
-                    @Override
-                    public Object[] getServices () {
-                        return new Object[] {};
-                    }
-                },
-                configuration
+            @Override
+            public String getTypeID () {
+                return "CPPLiteSession";
             }
+
+            @Override
+            public Object[] getServices () {
+                return new Object[] {};
+            }
+        };
+        Object[] allServices = Arrays.copyOf(services, services.length + 2);
+        allServices[services.length] = sessionProvider;
+        allServices[services.length + 1] = configuration;
+        DebuggerInfo di = DebuggerInfo.create(
+            "CPPLiteDebuggerInfo",
+            allServices
         );
         DebuggerEngine[] es = DebuggerManager.getDebuggerManager ().
             startDebugging (di);
         Pty pty = PtySupport.allocate(ExecutionEnvironmentFactory.getLocal());
         CPPLiteDebugger debugger = es[0].lookupFirst(null, CPPLiteDebugger.class);
         List<String> executable = new ArrayList<>();
-        executable.add("gdb");
+        executable.add(configuration.getDebugger());
         executable.add("--interpreter=mi");
         executable.add("--tty=" + pty.getSlaveName());
         executable.addAll(configuration.getExecutable());
@@ -700,30 +755,27 @@ public final class CPPLiteDebugger {
         }
 
         private void addBreakpoint(CPPLiteBreakpoint breakpoint) {
-            Line l = breakpoint.getLine();
-            FileObject source = l.getLookup().lookup(FileObject.class);
-            File sourceFile = source != null ? FileUtil.toFile(source) : null;
-            if (sourceFile != null) {
-                String disabled = breakpoint.isEnabled() ? "" : "-d ";
-                Command command = new Command("-break-insert " + disabled + sourceFile.getAbsolutePath() + ":" + (l.getLineNumber() + 1)) {
-                    @Override
-                    protected void onDone(MIRecord record) {
-                        MIValue bkpt = record.results().valueOf("bkpt");
-                        if (bkpt instanceof MITList) {
-                            breakpointResolved(breakpoint, (MITList) bkpt);
-                        }
-                        super.onDone(record);
+            String path = breakpoint.getFilePath();
+            int lineNumber = breakpoint.getLineNumber();
+            String disabled = breakpoint.isEnabled() ? "" : "-d ";
+            Command command = new Command("-break-insert " + disabled + path + ":" + lineNumber) {
+                @Override
+                protected void onDone(MIRecord record) {
+                    MIValue bkpt = record.results().valueOf("bkpt");
+                    if (bkpt instanceof MITList) {
+                        breakpointResolved(breakpoint, (MITList) bkpt);
                     }
+                    super.onDone(record);
+                }
 
-                    @Override
-                    protected void onError(MIRecord record) {
-                        String msg = record.results().getConstValue("msg");
-                        breakpointError(breakpoint, msg);
-                        super.onError(record);
-                    }
-                };
-                proxy.send(command);
-            }
+                @Override
+                protected void onError(MIRecord record) {
+                    String msg = record.results().getConstValue("msg");
+                    breakpointError(breakpoint, msg);
+                    super.onError(record);
+                }
+            };
+            proxy.send(command, false);
             breakpoint.addPropertyChangeListener(this);
         }
 
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java
index f88b745..d604d12 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java
@@ -26,14 +26,16 @@ import java.util.List;
  *
  * @author lahvac
  */
-public class CPPLiteDebuggerConfig {
+public final class CPPLiteDebuggerConfig {
 
     private final List<String> executable;
     private final File directory;
+    private final String debugger;
 
-    public CPPLiteDebuggerConfig(List<String> executable, File directory) {
+    public CPPLiteDebuggerConfig(List<String> executable, File directory, String debugger) {
         this.executable = executable;
         this.directory = directory;
+        this.debugger = debugger;
     }
 
     public String getDisplayName() {
@@ -47,4 +49,8 @@ public class CPPLiteDebuggerConfig {
     public File getDirectory() {
         return directory;
     }
+
+    public String getDebugger() {
+        return debugger;
+    }
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPThread.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPThread.java
index 6158f6e..cfaa991 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPThread.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPThread.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.cpplite.debugger;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.util.AbstractList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -230,8 +231,14 @@ public final class CPPThread implements DVThread {
                 stack[0] = topFrame;
                 i++;
             }
-            for (; i < l; i++) {
-                stack[i] = new CPPFrame(this, (MITList) ((MIResult) stackList.get(i)).value());
+            for (int li = i; li < l; li++) {
+                CPPFrame frame = CPPFrame.create(this, (MITList) ((MIResult) stackList.get(li)).value());
+                if (frame != null) {
+                    stack[i++] = frame;
+                }
+            }
+            if (i < l) {
+                stack = Arrays.copyOf(stack, i);
             }
             this.stack = stack;
         }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java
index cd5c6fe..ec1ef37 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java
@@ -18,27 +18,33 @@
  */
 package org.netbeans.modules.cpplite.debugger;
 
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Objects;
 
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst;
+import org.netbeans.modules.cnd.debugger.gdb2.mi.MIRecord;
 import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
 
 /**
  * Representation of a variable.
  */
-public final class CPPVariable {
+public final class CPPVariable implements NIVariable {
 
     private final CPPFrame frame;
+    private final CPPVariable parentVariable;
     private final String uniqueName;
     private final String name;
     private final String type;
     private final String value;
     private final int numChildren;
-    private volatile Map<String, CPPVariable> children;
+    private volatile Map<String, NIVariable> children;
 
-    CPPVariable(CPPFrame frame, String uniqueName, String name, String type, MIValue value, int numChildren) {
+    CPPVariable(CPPFrame frame, CPPVariable parentVariable, String uniqueName, String name, String type, MIValue value, int numChildren) {
         this.frame = frame;
+        this.parentVariable = parentVariable;
         this.uniqueName = uniqueName;
         this.name = name;
         this.type = type;
@@ -46,28 +52,42 @@ public final class CPPVariable {
         this.numChildren = numChildren;
     }
 
+    @Override
+    public NIFrame getFrame() {
+        return frame.getFrame();
+    }
+
+    @Override
+    public CPPVariable getParent() {
+        return parentVariable;
+    }
+
     public String getUniqueName() {
         return uniqueName;
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    @Override
     public String getType() {
         return type;
     }
 
+    @Override
     public String getValue() {
         return value;
     }
 
+    @Override
     public int getNumChildren() {
         return numChildren;
     }
 
-    public Map<String, CPPVariable> getChildrenVariables() {
-        Map<String, CPPVariable> vars = children;
+    public Map<String, NIVariable> getChildrenByNames() {
+        Map<String, NIVariable> vars = children;
         if (vars == null) {
             synchronized (this) {
                 vars = children;
@@ -78,4 +98,35 @@ public final class CPPVariable {
         }
         return vars;
     }
+
+    @Override
+    public NIVariable[] getChildren(int from, int to) {
+        Map<String, NIVariable> childrenVariables = getChildrenByNames();
+        NIVariable[] array = childrenVariables.values().toArray(new NIVariable[0]);
+        if (array.length == 1 && array[0] == null) {
+            return new NIVariable[0]; // Error
+        }
+        if (from >= 0) {
+            to = Math.min(to, array.length);
+            if (from < to) {
+                array = Arrays.copyOfRange(array, from, to);
+            } else {
+                array = new NIVariable[0];
+            }
+        }
+        return array;
+    }
+
+    @Override
+    public String getExpressionPath() {
+        MIRecord pathRecord;
+        try {
+            pathRecord = frame.getThread().getDebugger().sendAndGet("-var-info-path-expression " + uniqueName);
+        } catch (InterruptedException ex) {
+            return null;
+        }
+        String pathExpression = pathRecord.results().getConstValue("path_expr");
+        return pathExpression;
+    }
+
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/DebuggerBreakpointAnnotation.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/DebuggerBreakpointAnnotation.java
index f5e90b3..a169105 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/DebuggerBreakpointAnnotation.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/DebuggerBreakpointAnnotation.java
@@ -21,12 +21,14 @@ package org.netbeans.modules.cpplite.debugger;
 
 import java.util.LinkedList;
 import java.util.List;
+import org.netbeans.api.annotations.common.CheckForNull;
 
 import org.netbeans.api.debugger.Breakpoint;
 import org.netbeans.api.debugger.Breakpoint.HIT_COUNT_FILTERING_STYLE;
 import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
 import org.netbeans.spi.debugger.ui.BreakpointAnnotation;
 import org.openide.text.Annotatable;
+import org.openide.text.Line;
 import org.openide.util.NbBundle;
 
 
@@ -49,13 +51,21 @@ public class DebuggerBreakpointAnnotation extends BreakpointAnnotation {
     private final String type;
     private final CPPLiteBreakpoint breakpoint;
 
-    public DebuggerBreakpointAnnotation (String type, CPPLiteBreakpoint b) {
+    private DebuggerBreakpointAnnotation (String type, Annotatable annotatable, CPPLiteBreakpoint b) {
         this.type = type;
         this.breakpoint = b;
-        Annotatable annotatable = b.getLine ();
         attach (annotatable);
     }
 
+    @CheckForNull
+    public static DebuggerBreakpointAnnotation create(String type, CPPLiteBreakpoint b) {
+        Line line = b.getLine();
+        if (line == null) {
+            return null;
+        }
+        return new DebuggerBreakpointAnnotation(type, line, b);
+    }
+
     @Override
     public String getAnnotationType () {
         return type;
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ThreadsCollector.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ThreadsCollector.java
index 4689901..d39efa8 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ThreadsCollector.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ThreadsCollector.java
@@ -68,7 +68,7 @@ public final class ThreadsCollector {
         }
     }
 
-    CPPThread get(String id) {
+    public CPPThread get(String id) {
         synchronized (threads) {
             return threads.get(id);
         }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ToolTipAnnotation.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ToolTipAnnotation.java
index 88f571c..9fadadb 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ToolTipAnnotation.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ToolTipAnnotation.java
@@ -37,6 +37,7 @@ import org.openide.util.RequestProcessor;
 import org.netbeans.api.debugger.DebuggerManager;
 
 import org.netbeans.spi.debugger.ui.EditorContextDispatcher;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
 
 
 public class ToolTipAnnotation extends Annotation implements Runnable {
@@ -96,8 +97,11 @@ public class ToolTipAnnotation extends Annotation implements Runnable {
         if (d == null || (frame = d.getCurrentFrame()) == null) {
             return;
         }
-        frame.evaluateLazy(expression,
-                           variable -> {
+        frame.evaluateAsync(expression).thenAccept(variable -> {
+                               VariableDisplayer displayer = currentEngine.lookupFirst(null, VariableDisplayer.class);
+                               if (displayer != null) {
+                                   variable = displayer.displayed(variable)[0];
+                               }
                                String value = variable.getValue();
                                if (!value.equals(expression)) {
                                    String toolTipText;
@@ -109,9 +113,10 @@ public class ToolTipAnnotation extends Annotation implements Runnable {
                                    }
                                    firePropertyChange (PROP_SHORT_DESCRIPTION, null, toolTipText);
                                }
-                           }, exception -> {
+                           }).exceptionally(exception -> {
                                String toolTipText = exception.getLocalizedMessage();
                                firePropertyChange (PROP_SHORT_DESCRIPTION, null, toolTipText);
+                               return null;
                            });
     }
 
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java
index f4f111f..03e5c62 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java
@@ -21,8 +21,10 @@ package org.netbeans.modules.cpplite.debugger.api;
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import org.netbeans.api.debugger.DebuggerEngine;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebuggerConfig;
+import org.openide.util.Pair;
 
 /**
  *
@@ -36,6 +38,8 @@ public class Debugger {
     }
 
     public static Process startInDebugger(List<String> command, File directory) throws IOException {
-        return CPPLiteDebugger.startDebugging(new CPPLiteDebuggerConfig(command, directory)).second();
+        Pair<DebuggerEngine, Process> engineProcess = CPPLiteDebugger.startDebugging(new CPPLiteDebuggerConfig(command, directory, "gdb"));
+        engineProcess.first().lookupFirst(null, CPPLiteDebugger.class).execRun();
+        return engineProcess.second();
     }
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointAnnotationProvider.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointAnnotationProvider.java
index aac9137..b55783f 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointAnnotationProvider.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointAnnotationProvider.java
@@ -104,7 +104,7 @@ public class BreakpointAnnotationProvider extends DebuggerManagerAdapter impleme
             for (Breakpoint breakpoint : DebuggerManager.getDebuggerManager().getBreakpoints()) {
                 if (breakpoint instanceof CPPLiteBreakpoint) {
                     CPPLiteBreakpoint b = (CPPLiteBreakpoint) breakpoint;
-                    if (isAt(b, fo)) {
+                    if (!b.isHidden() && isAt(b, fo)) {
                         if (!breakpointToAnnotations.containsKey(b)) {
                             b.addPropertyChangeListener(this);
                         }
@@ -118,13 +118,13 @@ public class BreakpointAnnotationProvider extends DebuggerManagerAdapter impleme
     }
 
     private static boolean isAt(CPPLiteBreakpoint b, FileObject fo) {
-        FileObject bfo = (FileObject) b.getLine().getLookup().lookup(FileObject.class);
+        FileObject bfo = b.getFileObject();
         return fo.equals(bfo);
     }
 
     @Override
     public void breakpointAdded(Breakpoint breakpoint) {
-        if (breakpoint instanceof CPPLiteBreakpoint) {
+        if (breakpoint instanceof CPPLiteBreakpoint && !((CPPLiteBreakpoint) breakpoint).isHidden()) {
             postAnnotationRefresh((CPPLiteBreakpoint) breakpoint, false, true);
             breakpoint.addPropertyChangeListener (this);
         }
@@ -132,7 +132,7 @@ public class BreakpointAnnotationProvider extends DebuggerManagerAdapter impleme
 
     @Override
     public void breakpointRemoved(Breakpoint breakpoint) {
-        if (breakpoint instanceof CPPLiteBreakpoint) {
+        if (breakpoint instanceof CPPLiteBreakpoint && !((CPPLiteBreakpoint) breakpoint).isHidden()) {
             breakpoint.removePropertyChangeListener (this);
             postAnnotationRefresh((CPPLiteBreakpoint) breakpoint, true, false);
         }
@@ -231,7 +231,10 @@ public class BreakpointAnnotationProvider extends DebuggerManagerAdapter impleme
         String condition = getCondition(b);
         boolean isConditional = condition.trim().length() > 0 || b.getHitCountFilteringStyle() != null;
         String annotationType = getAnnotationType(b, isConditional, breakpointsActive);
-        DebuggerBreakpointAnnotation annotation = new DebuggerBreakpointAnnotation (annotationType, b);
+        DebuggerBreakpointAnnotation annotation = DebuggerBreakpointAnnotation.create(annotationType, b);
+        if (annotation == null) {
+            return ;
+        }
         Set<Annotation> bpAnnotations = breakpointToAnnotations.get(b);
         if (bpAnnotations == null) {
             Set<Annotation> set = new WeakSet<>();
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointModel.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointModel.java
index 6939797..11d1757 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointModel.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointModel.java
@@ -19,6 +19,7 @@
 
 package org.netbeans.modules.cpplite.debugger.breakpoints;
 
+import java.io.File;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -65,9 +66,15 @@ public class BreakpointModel implements NodeModel {
     public String getDisplayName (Object node) throws UnknownTypeException {
         if (node instanceof CPPLiteBreakpoint) {
             CPPLiteBreakpoint breakpoint = (CPPLiteBreakpoint) node;
-            FileObject fileObject = breakpoint.getLine().getLookup().lookup(FileObject.class);
-            return fileObject.getNameExt () + ":" + 
-                (breakpoint.getLine ().getLineNumber () + 1);
+            String nameExt;
+            FileObject fileObject = breakpoint.getFileObject();
+            if (fileObject != null) {
+                nameExt = fileObject.getNameExt();
+            } else {
+                File file = new File(breakpoint.getFilePath());
+                nameExt = file.getName();
+            }
+            return nameExt + ":" + breakpoint.getLineNumber();
         }
         throw new UnknownTypeException (node);
     }
@@ -114,7 +121,7 @@ public class BreakpointModel implements NodeModel {
     throws UnknownTypeException {
         if (node instanceof CPPLiteBreakpoint) {
             CPPLiteBreakpoint breakpoint = (CPPLiteBreakpoint) node;
-            return breakpoint.getLine ().getDisplayName ();
+            return breakpoint.getFilePath() + ":" + breakpoint.getLineNumber();
         }
         throw new UnknownTypeException (node);
     }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointsReader.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointsReader.java
index e0d9020..ae26933 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointsReader.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/BreakpointsReader.java
@@ -21,42 +21,50 @@ package org.netbeans.modules.cpplite.debugger.breakpoints;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+
 import org.netbeans.api.debugger.Breakpoint;
 import org.netbeans.api.debugger.Properties;
 import org.netbeans.spi.debugger.DebuggerServiceRegistration;
-import org.openide.cookies.LineCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.filesystems.URLMapper;
-import org.openide.loaders.DataObject;
-import org.openide.loaders.DataObjectNotFoundException;
-import org.openide.text.Line;
-
 
-/**
- *
- * @author Jan Jancura
- */
 @DebuggerServiceRegistration(types={Properties.Reader.class})
 public class BreakpointsReader implements Properties.Reader {
-    
-    
+
     @Override
     public String [] getSupportedClassNames () {
         return new String[] {
-            CPPLiteBreakpoint.class.getName (), 
+            CPPLiteBreakpoint.class.getName (),
         };
     }
-    
+
     @Override
     public Object read (String typeID, Properties properties) {
         if (!(typeID.equals (CPPLiteBreakpoint.class.getName ())))
             return null;
-        
-        Line line = getLine (
-            properties.getString ("url", null),
-            properties.getInt ("lineNumber", 1));
-        if (line == null) return null;
-        CPPLiteBreakpoint b = new CPPLiteBreakpoint (line);
+
+        CPPLiteBreakpoint b;
+        int lineNumber = properties.getInt("lineNumber", 0) + 1;
+        String url = properties.getString ("url", null);
+        if (url != null) {
+            FileObject fo;
+            try {
+                fo = URLMapper.findFileObject(new URL(url));
+            } catch (MalformedURLException ex) {
+                fo = null;
+            }
+            if (fo == null) {
+                // The user file is gone
+                return null;
+            }
+            b = CPPLiteBreakpoint.create(fo, lineNumber);
+        } else {
+            String filePath = properties.getString ("filePath", null);
+            if (filePath == null) {
+                return null;
+            }
+            b = CPPLiteBreakpoint.create(filePath, lineNumber);
+        }
         b.setGroupName(
             properties.getString (Breakpoint.PROP_GROUP_NAME, "")
         );
@@ -79,18 +87,21 @@ public class BreakpointsReader implements Properties.Reader {
             b.disable ();
         return b;
     }
-    
+
     @Override
     public void write (Object object, Properties properties) {
         CPPLiteBreakpoint b = (CPPLiteBreakpoint) object;
-        FileObject fo = (FileObject) b.getLine().getLookup().lookup(FileObject.class);
-        properties.setString("url", fo.toURL().toString());
+        FileObject fo = b.getFileObject();
+        if (fo != null) {
+            properties.setString("url", fo.toURL().toString());
+        }
+        properties.setString("filePath", b.getFilePath());
         properties.setInt (
-            "lineNumber", 
-            b.getLine ().getLineNumber ()
+            "lineNumber",
+            b.getLineNumber() - 1
         );
         properties.setString (
-            Breakpoint.PROP_GROUP_NAME, 
+            Breakpoint.PROP_GROUP_NAME,
             b.getGroupName ()
         );
         properties.setBoolean (Breakpoint.PROP_ENABLED, b.isEnabled ());
@@ -103,32 +114,4 @@ public class BreakpointsReader implements Properties.Reader {
         }
         properties.setString(CPPLiteBreakpoint.PROP_CONDITION, condition);
     }
-    
-
-    private Line getLine (String url, int lineNumber) {
-        FileObject file;
-        try {
-            file = URLMapper.findFileObject (new URL (url));
-        } catch (MalformedURLException e) {
-            return null;
-        }
-        if (file == null) return null;
-        DataObject dataObject;
-        try {
-            dataObject = DataObject.find (file);
-        } catch (DataObjectNotFoundException ex) {
-            return null;
-        }
-        if (dataObject == null) return null;
-        LineCookie lineCookie = dataObject.getLookup().lookup(LineCookie.class);
-        if (lineCookie == null) return null;
-        Line.Set ls = lineCookie.getLineSet ();
-        if (ls == null) return null;
-        try {
-            return ls.getCurrent (lineNumber);
-        } catch (IndexOutOfBoundsException e) {
-        } catch (IllegalArgumentException e) {
-        }
-        return null;
-    }
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpoint.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpoint.java
index 6f063ce..f4e6eb0 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpoint.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpoint.java
@@ -19,12 +19,15 @@
 
 package org.netbeans.modules.cpplite.debugger.breakpoints;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.api.annotations.common.NullAllowed;
 import org.netbeans.api.debugger.Breakpoint;
 import org.netbeans.api.debugger.DebuggerEngine;
 import org.netbeans.api.debugger.DebuggerManager;
@@ -33,7 +36,11 @@ import org.netbeans.api.debugger.DebuggerManagerListener;
 import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectManager;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
+import org.openide.cookies.LineCookie;
 import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+import org.openide.loaders.DataObjectNotFoundException;
 import org.openide.text.Line;
 import org.openide.util.NbBundle;
 import org.openide.util.WeakListeners;
@@ -47,28 +54,101 @@ import org.openide.util.WeakListeners;
 public final class CPPLiteBreakpoint extends Breakpoint {
 
     public static final String PROP_CONDITION = "condition";                    // NOI18N
+    public static final String PROP_HIDDEN = "hidden";                          // NOI18N
 
-    private volatile boolean enabled = true;
-    private final Map<CPPLiteDebugger, String> ids = new HashMap<>();
-    private final Line line;
+    private final AtomicBoolean enabled = new AtomicBoolean(true);
+    private final AtomicBoolean hidden = new AtomicBoolean(false);
+    @NullAllowed
+    private final FileObject fileObject; // The user file that contains the breakpoint
+    private final String filePath; // Path of the file to which MI breakpoint is submitted
+    private final int lineNumber; // The breakpoint line number
     private volatile String condition;
 
-    public CPPLiteBreakpoint (Line line) {
-        this.line = line;
+    private CPPLiteBreakpoint (FileObject fileObject, String filePath, int lineNumber) {
+        this.fileObject = fileObject;
+        this.filePath = filePath;
+        this.lineNumber = lineNumber;
+    }
+
+    public static CPPLiteBreakpoint create(Line line) {
+        int lineNumber = line.getLineNumber() + 1;
+        FileObject fileObject = line.getLookup().lookup(FileObject.class);
+        String filePath = FileUtil.toFile(fileObject).getAbsolutePath();
+        return new CPPLiteBreakpoint(fileObject, filePath, lineNumber);
     }
 
-    public Line getLine () {
-        return line;
+    /**
+     * Create a new CPP lite breakpoint based on a user file.
+     * @param fileObject the file path of the breakpoint
+     * @param lineNumber 1-based line number
+     * @return a new breakpoint.
+     */
+    public static CPPLiteBreakpoint create(FileObject fileObject, int lineNumber) {
+        String filePath = FileUtil.toFile(fileObject).getAbsolutePath();
+        return new CPPLiteBreakpoint(fileObject, filePath, lineNumber);
     }
 
     /**
+     * Create a new CPP lite breakpoint, that is not associated with a user file.
+     * @param filePath the file path of the breakpoint in the debuggee
+     * @param lineNumber 1-based line number
+     * @return a new breakpoint.
+     */
+    public static CPPLiteBreakpoint create(String filePath, int lineNumber) {
+        return new CPPLiteBreakpoint(null, filePath, lineNumber);
+    }
+
+    /**
+     * Get the file path of the breakpoint in the debuggee.
+     */
+    public String getFilePath() {
+        return filePath;
+    }
+
+    /**
+     * 1-based line number.
+     */
+    public int getLineNumber() {
+        return lineNumber;
+    }
+
+    @CheckForNull
+    public FileObject getFileObject() {
+        return fileObject;
+    }
+
+    @CheckForNull
+    public Line getLine() {
+        FileObject fo = fileObject;
+        if (fo == null) {
+            return null;
+        }
+        DataObject dataObject;
+        try {
+            dataObject = DataObject.find(fo);
+        } catch (DataObjectNotFoundException ex) {
+            return null;
+        }
+        LineCookie lineCookie = dataObject.getLookup().lookup(LineCookie.class);
+        if (lineCookie != null) {
+            Line.Set ls = lineCookie.getLineSet ();
+            if (ls != null) {
+                try {
+                    return ls.getCurrent(lineNumber - 1);
+                } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+                }
+            }
+        }
+        return null;
+    }
+    /**
      * Test whether the breakpoint is enabled.
      *
      * @return <code>true</code> if so
      */
     @Override
     public boolean isEnabled () {
-        return enabled;
+        return enabled.get();
     }
 
     /**
@@ -76,9 +156,9 @@ public final class CPPLiteBreakpoint extends Breakpoint {
      */
     @Override
     public void disable () {
-        if (!enabled) return;
-        enabled = false;
-        firePropertyChange (PROP_ENABLED, Boolean.TRUE, Boolean.FALSE);
+        if (enabled.compareAndSet(true, false)) {
+            firePropertyChange (PROP_ENABLED, Boolean.TRUE, Boolean.FALSE);
+        }
     }
 
     /**
@@ -86,9 +166,9 @@ public final class CPPLiteBreakpoint extends Breakpoint {
      */
     @Override
     public void enable () {
-        if (enabled) return;
-        enabled = true;
-        firePropertyChange (PROP_ENABLED, Boolean.FALSE, Boolean.TRUE);
+        if (enabled.compareAndSet(false, true)) {
+            firePropertyChange (PROP_ENABLED, Boolean.FALSE, Boolean.TRUE);
+        }
     }
 
     /**
@@ -117,6 +197,27 @@ public final class CPPLiteBreakpoint extends Breakpoint {
         setValidity(validity, reason);
     }
 
+    /**
+     * Gets value of hidden property.
+     *
+     * @return value of hidden property
+     */
+    public boolean isHidden() {
+        return hidden.get();
+    }
+
+    /**
+     * Sets value of hidden property.
+     *
+     * @param h a new value of hidden property
+     */
+    public void setHidden(boolean h) {
+        boolean old = hidden.getAndSet(h);
+        if (old != h) {
+            firePropertyChange(PROP_HIDDEN, old, h);
+        }
+    }
+
     @Override
     public GroupProperties getGroupProperties() {
         return new CPPGroupProperties();
@@ -137,7 +238,7 @@ public final class CPPLiteBreakpoint extends Breakpoint {
         }
 
         private FileObject getFile() {
-            return line.getLookup().lookup(FileObject.class);
+            return FileUtil.toFileObject(new File(filePath));
         }
 
         @Override
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpointActionProvider.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpointActionProvider.java
index c166e25..2402fe0 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpointActionProvider.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/CPPLiteBreakpointActionProvider.java
@@ -69,23 +69,30 @@ public class CPPLiteBreakpointActionProvider extends ActionsProviderSupport
     @Override
     public void doAction (Object action) {
         Line line = getCurrentLine ();
-        if (line == null) return ;
-        Breakpoint[] breakpoints = DebuggerManager.getDebuggerManager ().
-            getBreakpoints ();
+        if (line == null) {
+            return ;
+        }
+        Breakpoint[] breakpoints = DebuggerManager.getDebuggerManager().getBreakpoints ();
+        FileObject fo = line.getLookup().lookup(FileObject.class);
+        if (fo == null) {
+            return ;
+        }
+        int lineNumber = line.getLineNumber() + 1;
         int i, k = breakpoints.length;
-        for (i = 0; i < k; i++)
-            if ( breakpoints [i] instanceof CPPLiteBreakpoint &&
-                 ((CPPLiteBreakpoint) breakpoints [i]).getLine ().equals (line)
-            ) {
-                DebuggerManager.getDebuggerManager ().removeBreakpoint
-                    (breakpoints [i]);
-                break;
+        for (i = 0; i < k; i++) {
+            if (breakpoints[i] instanceof CPPLiteBreakpoint) {
+                CPPLiteBreakpoint cppb = (CPPLiteBreakpoint) breakpoints[i];
+                if (fo.equals(cppb.getFileObject()) && cppb.getLineNumber() == lineNumber) {
+                    DebuggerManager.getDebuggerManager().removeBreakpoint(cppb);
+                    break;
+                }
             }
-        if (i == k)
+        }
+        if (i == k) {
             DebuggerManager.getDebuggerManager ().addBreakpoint (
-                new CPPLiteBreakpoint (line)
+                CPPLiteBreakpoint.create(line)
             );
-        //S ystem.out.println("toggle");
+        }
     }
 
     /**
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/PersistenceManager.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/PersistenceManager.java
index d467600..d77c8f2 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/PersistenceManager.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/breakpoints/PersistenceManager.java
@@ -136,12 +136,15 @@ public class PersistenceManager implements LazyDebuggerManagerListener {
     private static Breakpoint[] getBreakpoints () {
         Breakpoint[] bs = DebuggerManager.getDebuggerManager ().
             getBreakpoints ();
-        int i, k = bs.length;
         List<Breakpoint> bb = new ArrayList<>();
-        for (i = 0; i < k; i++)
-            // Don't store hidden breakpoints
-            if (bs[i] instanceof CPPLiteBreakpoint)
-                bb.add (bs [i]);
+        for (Breakpoint b : bs) {
+            if (b instanceof CPPLiteBreakpoint) {
+                // Don't store hidden breakpoints
+                if (!((CPPLiteBreakpoint) b).isHidden()) {
+                    bb.add(b);
+                }
+            }
+        }
         bs = new Breakpoint [bb.size ()];
         return (Breakpoint[]) bb.toArray (bs);
     }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/debuggingview/DebuggingModel.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/debuggingview/DebuggingModel.java
index 1db0f44..c86d1d5 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/debuggingview/DebuggingModel.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/debuggingview/DebuggingModel.java
@@ -128,7 +128,7 @@ public class DebuggingModel extends CachedChildrenTreeModel implements ExtendedN
             return ((CPPThread) node).getName();
         } else if (node instanceof CPPFrame) {
             CPPFrame frame = (CPPFrame) node;
-            return frame.functionName + "; " + frame.shortFileName + ":" + frame.line;
+            return frame.getName();
         }
         throw new UnknownTypeException (node);
     }
@@ -143,7 +143,7 @@ public class DebuggingModel extends CachedChildrenTreeModel implements ExtendedN
             return details;
         } else if (node instanceof CPPFrame) {
             CPPFrame frame = (CPPFrame) node;
-            return frame.functionName + "; " + frame.fullFileName + ":" + frame.line;
+            return frame.getDescription();
         }
         throw new UnknownTypeException (node);
     }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/CallStackModel.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/CallStackModel.java
index 017bef6..30768d9 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/CallStackModel.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/CallStackModel.java
@@ -19,6 +19,8 @@
 
 package org.netbeans.modules.cpplite.debugger.models;
 
+import java.net.MalformedURLException;
+import java.net.URI;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import javax.swing.Action;
@@ -39,6 +41,9 @@ import org.netbeans.spi.viewmodel.TreeModel;
 import org.netbeans.spi.viewmodel.ModelListener;
 import org.netbeans.spi.viewmodel.UnknownTypeException;
 import org.netbeans.spi.debugger.ui.Constants;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
 
 import org.openide.text.Line;
 import org.openide.util.NbBundle;
@@ -190,7 +195,7 @@ public class CallStackModel implements TreeModel, NodeModel, NodeActionsProvider
     public String getDisplayName (Object node) throws UnknownTypeException {
         if (node instanceof CPPFrame) {
             CPPFrame frame = (CPPFrame) node;
-            return frame.functionName + "; " + frame.shortFileName + ":" + frame.line;
+            return frame.getName();
         }
         if (node == ROOT) {
             return ROOT;
@@ -232,7 +237,7 @@ public class CallStackModel implements TreeModel, NodeModel, NodeActionsProvider
     public String getShortDescription (Object node) throws UnknownTypeException {
         if (node instanceof CPPFrame) {
             CPPFrame frame = (CPPFrame) node;
-            return frame.functionName + "; " + frame.fullFileName + ":" + frame.line;
+            return frame.getDescription();
         }
         throw new UnknownTypeException (node);
     }
@@ -295,7 +300,23 @@ public class CallStackModel implements TreeModel, NodeModel, NodeActionsProvider
         if (columnID == Constants.CALL_STACK_FRAME_LOCATION_COLUMN_ID) {
             if (node instanceof CPPFrame) {
                 CPPFrame frame = (CPPFrame) node;
-                return frame.fullFileName + ":" + frame.line;
+                URI sourceURI = frame.getSourceURI();
+                if (sourceURI == null) {
+                    return "";
+                }
+                String sourceName;
+                try {
+                    FileObject file = URLMapper.findFileObject(sourceURI.toURL());
+                    sourceName = file.getPath();
+                } catch (MalformedURLException ex) {
+                    sourceName = sourceURI.toString();
+                }
+                int line = frame.getLine();
+                if (line > 0) {
+                    return sourceName + ':' + line;
+                } else {
+                    return sourceName + ":?";
+                }
             }
         }
         throw new UnknownTypeException (node);
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/VariablesModel.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/VariablesModel.java
index cc0666b..ed47d5a 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/VariablesModel.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/VariablesModel.java
@@ -27,7 +27,7 @@ import org.netbeans.modules.cpplite.debugger.CPPFrame;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger.StateListener;
 import org.netbeans.modules.cpplite.debugger.CPPThread;
-import org.netbeans.modules.cpplite.debugger.CPPVariable;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
 
 import org.netbeans.spi.debugger.ContextProvider;
 import org.netbeans.spi.debugger.DebuggerServiceRegistration;
@@ -40,6 +40,7 @@ import org.netbeans.spi.viewmodel.UnknownTypeException;
 import org.openide.util.NbBundle;
 
 import org.openide.util.WeakListeners;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
 
 /**
  *
@@ -56,12 +57,14 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
 
     private final CPPLiteDebugger       debugger;
     private final List<ModelListener>   listeners = new CopyOnWriteArrayList<>();
+    private final VariableDisplayer    displayer;
     private volatile CPPFrame           currentFrame;
 
 
     public VariablesModel (ContextProvider contextProvider) {
         debugger = contextProvider.lookupFirst(null, CPPLiteDebugger.class);
         debugger.addStateListener(WeakListeners.create(StateListener.class, this, debugger));
+        displayer = contextProvider.lookupFirst(null, VariableDisplayer.class);
         currentFrame = debugger.getCurrentFrame();
     }
 
@@ -96,24 +99,31 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
      */
     @Override
     public Object[] getChildren (Object parent, int from, int to) throws UnknownTypeException {
-        CPPVariable parentVar;
+        NIVariable parentVar;
         if (parent == ROOT) {
             parentVar = null;
-        } else if (parent instanceof CPPVariable) {
-            parentVar = (CPPVariable) parent;
+        } else if (parent instanceof NIVariable) {
+            parentVar = (NIVariable) parent;
         } else {
             throw new UnknownTypeException (parent);
         }
         CPPFrame frame = currentFrame;
         if (frame != null) {
-            Map<String, CPPVariable> variables = (parentVar == null) ? frame.getVariables() : parentVar.getChildrenVariables();
-            Object[] array = variables.values().toArray();
-            if (array.length == 1 && array[0] == null) {
-                // Some error / message
-                return new Object[]{variables.keySet().iterator().next()};
+            NIVariable[] array;
+            if (parentVar == null) {
+                Map<String, NIVariable> variables = frame.getVariables();
+                array = variables.values().toArray(new NIVariable[0]);
+                if (array.length == 1 && array[0] == null) {
+                    // Some error / message
+                    return new Object[]{variables.keySet().iterator().next()};
+                }
             } else {
-                return array;
+                array = parentVar.getChildren(from, to);
             }
+            if (displayer != null) {
+                array = displayer.displayed(array);
+            }
+            return array;
         } else {
             return NO_VARS;
         }
@@ -134,8 +144,8 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
         if (node instanceof String) {
             return true;
         }
-        if (node instanceof CPPVariable) {
-            return ((CPPVariable) node).getNumChildren() == 0;
+        if (node instanceof NIVariable) {
+            return ((NIVariable) node).getNumChildren() == 0;
         }
         throw new UnknownTypeException (node);
     }
@@ -158,8 +168,8 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
     public int getChildrenCount (Object node) throws UnknownTypeException {
         if (node == ROOT) {
             return Integer.MAX_VALUE;
-        } else if (node instanceof CPPVariable) {
-            return ((CPPVariable) node).getNumChildren();
+        } else if (node instanceof NIVariable) {
+            return ((NIVariable) node).getNumChildren();
         }
         throw new UnknownTypeException (node);
     }
@@ -201,8 +211,8 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
         if (node instanceof String) {
             return (String) node;
         }
-        if (node instanceof CPPVariable) {
-            return ((CPPVariable) node).getName();
+        if (node instanceof NIVariable) {
+            return ((NIVariable) node).getName();
         }
         throw new UnknownTypeException (node);
     }
@@ -218,7 +228,7 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
      */
     @Override
     public String getIconBase (Object node) throws UnknownTypeException {
-        if (node instanceof CPPVariable) {
+        if (node instanceof NIVariable) {
             return LOCAL;
         }
         if (node instanceof String) {
@@ -265,13 +275,13 @@ public class VariablesModel implements TreeModel, NodeModel, TableModel, StateLi
     @Override
     public Object getValueAt (Object node, String columnID) throws UnknownTypeException {
         if (columnID.equals ("LocalsValue")) {
-            if (node instanceof CPPVariable) {
-                return ((CPPVariable) node).getValue();
+            if (node instanceof NIVariable) {
+                return ((NIVariable) node).getValue();
             }
         }
         if (columnID.equals ("LocalsType")) {
-            if (node instanceof CPPVariable) {
-                return ((CPPVariable) node).getType();
+            if (node instanceof NIVariable) {
+                return ((NIVariable) node).getType();
             }
         }
         if (node instanceof String) {
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/WatchesModel.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/WatchesModel.java
index abf9740..56707c4 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/WatchesModel.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/models/WatchesModel.java
@@ -32,8 +32,8 @@ import org.netbeans.modules.cpplite.debugger.CPPFrame;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
 import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger.StateListener;
 import org.netbeans.modules.cpplite.debugger.CPPThread;
-import org.netbeans.modules.cpplite.debugger.CPPVariable;
-import org.netbeans.modules.cpplite.debugger.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
 import org.netbeans.spi.debugger.ContextProvider;
 import org.netbeans.spi.debugger.DebuggerServiceRegistration;
 import org.netbeans.spi.viewmodel.ModelEvent;
@@ -99,7 +99,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
         if (ew != null) {
             switch (ew.getStatus()) {
                 case READY:
-                    CPPVariable result = ew.getResult();
+                    NIVariable result = ew.getResult();
                     return result.getNumChildren();
             }
         }
@@ -115,7 +115,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
         if (ew != null) {
             switch (ew.getStatus()) {
                 case READY:
-                    CPPVariable result = ew.getResult();
+                    NIVariable result = ew.getResult();
                     return result.getNumChildren() == 0;
             }
         }
@@ -171,7 +171,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
             ew.startEvaluate();
             switch (ew.getStatus()) {
                 case READY:
-                    CPPVariable result = ew.getResult();
+                    NIVariable result = ew.getResult();
                     return ew.getExpression() + " = " + result.getValue();
                 case FAILED:
                     EvaluateException exc = ew.getException();
@@ -212,7 +212,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
                 ew.startEvaluate();
                 switch (ew.getStatus()) {
                     case READY:
-                        CPPVariable result = ew.getResult();
+                        NIVariable result = ew.getResult();
                         if (showValue) {
                             return result.getValue();
                         } else {
@@ -342,7 +342,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
         private final Watch watch;
         private volatile AtomicReference<EvalStatus> status = new AtomicReference<>(EvalStatus.NEW);
         private volatile String expression;
-        private volatile CPPVariable result;
+        private volatile NIVariable result;
         private volatile EvaluateException exception;
 
         private EvalWatch(Watch watch) {
@@ -364,16 +364,17 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
                 exception = null;
                 String expression = watch.getExpression();
                 this.expression = expression;
-                frame.evaluateLazy(expression,
-                                   (CPPVariable variable) -> {
+                frame.evaluateAsync(expression).thenAccept(
+                                   (NIVariable variable) -> {
                                        result = variable;
                                        status.set(EvalStatus.READY);
                                        fireChanged(watch);
-                                   },
-                                   (EvaluateException exc) -> {
-                                       exception = exc;
+                                   }).exceptionally(
+                                   exc -> {
+                                       exception = (EvaluateException) exc;
                                        status.set(EvalStatus.FAILED);
                                        fireChanged(watch);
+                                       return null;
                                    });
             }
         }
@@ -382,7 +383,7 @@ public class WatchesModel implements TreeModelFilter, NodeModelFilter, TableMode
             return expression;
         }
 
-        CPPVariable getResult() {
+        NIVariable getResult() {
             return result;
         }
 
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIBreakpoints.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIBreakpoints.java
new file mode 100644
index 0000000..754ccc6
--- /dev/null
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIBreakpoints.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cpplite.debugger.ni;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+
+/**
+ *
+ * @author martin
+ */
+final class NIBreakpoints {
+
+    private final Map<Object, CPPLiteBreakpoint> ni2C = new IdentityHashMap<>();
+
+    Breakpoint addLineBreakpoint(Object key, NILineBreakpointDescriptor bd) {
+        CPPLiteBreakpoint breakpoint;
+        boolean isNew;
+        synchronized (ni2C) {
+            breakpoint = ni2C.get(key);
+            isNew = breakpoint == null;
+            if (isNew) {
+                breakpoint = CPPLiteBreakpoint.create(bd.getFilePath(), bd.getLine());
+                ni2C.put(key, breakpoint);
+            }
+        }
+        // TODO Update fileUrl and line number
+        if (bd.isEnabled()) {
+            breakpoint.enable();
+        } else {
+            breakpoint.disable();
+        }
+        breakpoint.setCondition(bd.getCondition());
+        breakpoint.setHidden(bd.isHidden());
+        if (isNew) {
+            DebuggerManager.getDebuggerManager().addBreakpoint(breakpoint);
+        }
+        return breakpoint;
+    }
+
+    void removeBreakpoint(Object key) {
+        CPPLiteBreakpoint breakpoint;
+        synchronized (ni2C) {
+            breakpoint = ni2C.remove(key);
+        }
+        if (breakpoint != null) {
+            DebuggerManager.getDebuggerManager().removeBreakpoint(breakpoint);
+        }
+    }
+
+    void dispose() {
+        List<CPPLiteBreakpoint> cbs;
+        synchronized (ni2C) {
+            cbs = new ArrayList<>(ni2C.size());
+            cbs.addAll(ni2C.values());
+            ni2C.clear();
+        }
+        for (CPPLiteBreakpoint cb : cbs) {
+            DebuggerManager.getDebuggerManager().removeBreakpoint(cb);
+        }
+    }
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java
new file mode 100644
index 0000000..f051d93
--- /dev/null
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cpplite.debugger.ni;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerEngine;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.extexecution.ExecutionService;
+import org.netbeans.modules.cpplite.debugger.CPPFrame;
+import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger;
+import org.netbeans.modules.cpplite.debugger.CPPLiteDebuggerConfig;
+import org.netbeans.modules.cpplite.debugger.CPPThread;
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+
+import org.openide.LifecycleManager;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+
+/**
+ *
+ * @author martin
+ */
+public class NIDebuggerProviderImpl implements NIDebuggerProvider {
+
+    private final NIBreakpoints breakpointsHandler = new NIBreakpoints();
+    private volatile CPPLiteDebugger debugger;
+    private volatile FrameDisplayer frameDisplayer;
+    private volatile VariableDisplayer variablesDisplayer;
+    private final RequestProcessor varDisplayerRP = new RequestProcessor(NIDebuggerProvider.class);
+
+    public NIDebuggerProviderImpl() {
+    }
+
+    @Override
+    public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor) {
+        return breakpointsHandler.addLineBreakpoint(id, breakpointDescriptor);
+    }
+
+    @Override
+    public void removeBreakpoint(Object id) {
+        breakpointsHandler.removeBreakpoint(id);
+    }
+
+    @Override
+    public void setFrameDisplayer(FrameDisplayer frameDisplayer) {
+        this.frameDisplayer = frameDisplayer;
+    }
+
+    @Override
+    public void setVariablesDisplayer(VariableDisplayer variablesDisplayer) {
+        this.variablesDisplayer = variablesDisplayer;
+    }
+
+    @Override
+    public CompletableFuture<Void> start(List<String> command, File workingDirectory, String miDebugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine) {
+        if (debugger != null) {
+            throw new IllegalStateException("Debugger has started already.");
+        }
+        if (executionDescriptor == null) {
+            executionDescriptor = new ExecutionDescriptor()
+                .showProgress(true)
+                .showSuspended(true)
+                .frontWindowOnError(true)
+                .controllable(true);
+        }
+        CompletableFuture<Void> completed = new CompletableFuture<>();
+        ExecutionService.newService(() -> {
+            LifecycleManager.getDefault().saveAll();
+            Pair<DebuggerEngine, Process> engineProcess = CPPLiteDebugger.startDebugging(
+                    new CPPLiteDebuggerConfig(command, workingDirectory, miDebugger),
+                    frameDisplayer,
+                    variablesDisplayer);
+            DebuggerEngine engine = engineProcess.first();
+            CPPLiteDebugger debugger = engine.lookupFirst(null, CPPLiteDebugger.class);
+            this.debugger = debugger;
+            if (startedEngine != null) {
+                startedEngine.accept(engine);
+            }
+            debugger.addStateListener(new CPPLiteDebugger.StateListener() {
+                @Override
+                public void currentThread(CPPThread thread) {}
+
+                @Override
+                public void currentFrame(CPPFrame frame) {}
+
+                @Override
+                public void suspended(boolean suspended) {}
+
+                @Override
+                public void finished() {
+                    breakpointsHandler.dispose();
+                    completed.complete(null);
+                }
+            });
+            debugger.execRun();
+            return engineProcess.second();
+        }, executionDescriptor, displayName).run();
+        return completed;
+    }
+
+    @Override
+    public CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName, NIFrame frame) {
+        CompletableFuture<NIVariable> result = new CompletableFuture<>();
+        CPPFrame cframe;
+        if (frame != null) {
+            CPPThread thread = debugger.getThreads().get(frame.getThreadId());
+            if (thread == null) {
+                result.completeExceptionally(new EvaluateException("No thread " + frame.getThreadId()));
+                return result;
+            }
+            try {
+                cframe = (CPPFrame) thread.getFrames().get(frame.getLevel());
+            } catch (IndexOutOfBoundsException ioobex) {
+                result.completeExceptionally(new EvaluateException(ioobex.getLocalizedMessage()));
+                return result;
+            }
+        } else {
+            cframe = debugger.getCurrentFrame();
+        }
+        cframe.evaluateAsync(expression, resultName).thenAccept(
+                rawResult -> {
+                    // We must not run the VariableDisplayer synchronously with the MI command thread
+                    varDisplayerRP.post(() -> {
+                        NIVariable[] variables = variablesDisplayer.displayed(rawResult);
+                        NIVariable variable = (variables.length > 0) ? variables[0] : rawResult;
+                        result.complete(variable);
+                    });
+                }).exceptionally(
+                exception -> {
+                    result.completeExceptionally(exception);
+                    return null;
+                });
+        return result;
+    }
+
+    @Override
+    public String readMemory(String address, long offset, int length) {
+        return debugger.readMemory(address, offset, length);
+    }
+
+    @Override
+    public String getVersion() {
+        return debugger.getVersion();
+    }
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerServiceProviderImpl.java
similarity index 61%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
copy to cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerServiceProviderImpl.java
index 0fc9c76..7a8be49 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerServiceProviderImpl.java
@@ -16,17 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.cpplite.debugger.ni;
 
-public final class EvaluateException extends Exception {
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerServiceProvider;
+import org.openide.util.lookup.ServiceProvider;
 
-    /**
-     * Constructs an instance of <code>EvaluateException</code> with the
-     * specified detail message.
-     *
-     * @param msg the detail message.
-     */
-    EvaluateException(String msg) {
-        super(msg);
+/**
+ *
+ * @author martin
+ */
+@ServiceProvider(service = NIDebuggerServiceProvider.class)
+public class NIDebuggerServiceProviderImpl implements NIDebuggerServiceProvider {
+
+    @Override
+    public NIDebuggerProvider create() {
+        return new NIDebuggerProviderImpl();
     }
+
 }
diff --git a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java
index 2d9d4bd..eb1e626 100644
--- a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java
+++ b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java
@@ -51,7 +51,7 @@ public abstract class AbstractDebugTest extends NbTestCase {
     }
 
     protected final void startDebugging(String name, File wd) throws IOException {
-        engine = CPPLiteDebugger.startDebugging(new CPPLiteDebuggerConfig(Arrays.asList(new File(wd, name).getAbsolutePath()), wd)).first();
+        engine = CPPLiteDebugger.startDebugging(new CPPLiteDebuggerConfig(Arrays.asList(new File(wd, name).getAbsolutePath()), wd, "gdb")).first();
         debugger = engine.lookupFirst(null, CPPLiteDebugger.class);
         debugger.addStateListener(new CPPLiteDebugger.StateListener() {
             @Override
@@ -80,6 +80,7 @@ public abstract class AbstractDebugTest extends NbTestCase {
             public void currentFrame(CPPFrame frame) {
             }
         });
+        debugger.execRun();
     }
 
     protected final void waitSuspended(int count) throws InterruptedException {
diff --git a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/BreakpointsTest.java b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/BreakpointsTest.java
index f80bee8..d22f578 100644
--- a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/BreakpointsTest.java
+++ b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/BreakpointsTest.java
@@ -76,8 +76,8 @@ public class BreakpointsTest extends AbstractDebugTest {
         compileC("breakpoints", wd);
         LineCookie lc = DataObject.find(source).getLookup().lookup(LineCookie.class);
         assertNotNull(lc);
-        CPPLiteBreakpoint bp8 = new CPPLiteBreakpoint(lc.getLineSet().getCurrent(7));
-        CPPLiteBreakpoint bp9 = new CPPLiteBreakpoint(lc.getLineSet().getCurrent(8));
+        CPPLiteBreakpoint bp8 = CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(7));
+        CPPLiteBreakpoint bp9 = CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(8));
         bp9.disable();
         DebuggerManager.getDebuggerManager().addBreakpoint(bp8);
         DebuggerManager.getDebuggerManager().addBreakpoint(bp9);
@@ -98,7 +98,7 @@ public class BreakpointsTest extends AbstractDebugTest {
         waitSuspended(3);
         assertStoppedAt(source.toURI(), 9);
 
-        CPPLiteBreakpoint bp10 = new CPPLiteBreakpoint(lc.getLineSet().getCurrent(9));
+        CPPLiteBreakpoint bp10 = CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(9));
         DebuggerManager.getDebuggerManager().addBreakpoint(bp10);
 
         engine.getActionsManager().doAction(ActionsManager.ACTION_CONTINUE);
@@ -116,7 +116,7 @@ public class BreakpointsTest extends AbstractDebugTest {
 
         bp10.disable();
 
-        bp8 = new CPPLiteBreakpoint(lc.getLineSet().getCurrent(7));
+        bp8 = CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(7));
         DebuggerManager.getDebuggerManager().addBreakpoint(bp8);
 
         engine.getActionsManager().doAction(ActionsManager.ACTION_CONTINUE);
diff --git a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/StepTest.java b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/StepTest.java
index a2042dd..307af7a 100644
--- a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/StepTest.java
+++ b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/StepTest.java
@@ -72,7 +72,7 @@ public class StepTest extends AbstractDebugTest {
         compileCPP("main", wd);
         LineCookie lc = DataObject.find(source).getLookup().lookup(LineCookie.class);
         assertNotNull(lc);
-        DebuggerManager.getDebuggerManager().addBreakpoint(new CPPLiteBreakpoint(lc.getLineSet().getCurrent(4)));
+        DebuggerManager.getDebuggerManager().addBreakpoint(CPPLiteBreakpoint.create(lc.getLineSet().getCurrent(4)));
         startDebugging("main", wd);
 
         waitSuspended(1);
diff --git a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/ActionProviderImpl.java b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/ActionProviderImpl.java
index cdcc5dc..428f4a8 100644
--- a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/ActionProviderImpl.java
+++ b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/ActionProviderImpl.java
@@ -90,7 +90,7 @@ public class ActionProviderImpl implements ActionProvider {
         }, executionDescriptor, ProjectUtils.getInformation(prj).getDisplayName() + " - " + command).run();
     }
 
-    private String quote(String s) {
+    private static String quote(String s) {
         return s.replace("_", "_u_").replace(" ", "_s_");
     }
 
diff --git a/ide/ide.kit/nbproject/project.xml b/ide/ide.kit/nbproject/project.xml
index cdd6261..3b19e47 100644
--- a/ide/ide.kit/nbproject/project.xml
+++ b/ide/ide.kit/nbproject/project.xml
@@ -195,6 +195,13 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.nativeimage.api</code-name-base>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.options.editor</code-name-base>
                     <run-dependency>
                         <release-version>1</release-version>
diff --git a/ide/nativeimage.api/build.xml b/ide/nativeimage.api/build.xml
new file mode 100644
index 0000000..13ce273
--- /dev/null
+++ b/ide/nativeimage.api/build.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
+<!-- for some information on what you could do (e.g. targets to override). -->
+<!-- If you delete this file and reopen the project it will be recreated. -->
+<project name="ide/nativeimage.api" default="build" basedir=".">
+    <description>Builds, tests, and runs the project org.netbeans.modules.nativeimage.api.</description>
+    <import file="../../nbbuild/templates/projectized.xml"/>
+</project>
diff --git a/ide/nativeimage.api/manifest.mf b/ide/nativeimage.api/manifest.mf
new file mode 100644
index 0000000..fdfa01c
--- /dev/null
+++ b/ide/nativeimage.api/manifest.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: false
+OpenIDE-Module: org.netbeans.modules.nativeimage.api/0
+OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nativeimage/api/Bundle.properties
+OpenIDE-Module-Specification-Version: 0.1
+
diff --git a/ide/nativeimage.api/nbproject/project.properties b/ide/nativeimage.api/nbproject/project.properties
new file mode 100644
index 0000000..5137752
--- /dev/null
+++ b/ide/nativeimage.api/nbproject/project.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+javac.source=1.8
+javac.compilerargs=-Xlint -Xlint:-serial
diff --git a/cpplite/cpplite.debugger/nbproject/project.xml b/ide/nativeimage.api/nbproject/project.xml
similarity index 55%
copy from cpplite/cpplite.debugger/nbproject/project.xml
copy to ide/nativeimage.api/nbproject/project.xml
index d360c06..f9f0149 100644
--- a/cpplite/cpplite.debugger/nbproject/project.xml
+++ b/ide/nativeimage.api/nbproject/project.xml
@@ -23,7 +23,7 @@
     <type>org.netbeans.modules.apisupport.project</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
-            <code-name-base>org.netbeans.modules.cpplite.debugger</code-name-base>
+            <code-name-base>org.netbeans.modules.nativeimage.api</code-name-base>
             <module-dependencies>
                 <dependency>
                     <code-name-base>org.netbeans.api.annotations.common</code-name-base>
@@ -40,15 +40,16 @@
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.63</specification-version>
+                        <specification-version>1.67</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.modules.dlight.nativeexecution</code-name-base>
+                    <code-name-base>org.netbeans.modules.extexecution</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>1.48</specification-version>
+                        <release-version>2</release-version>
+                        <specification-version>1.59</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -61,32 +62,6 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.spi.debugger.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>1</release-version>
-                        <specification-version>2.62</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.netbeans.spi.viewmodel</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <release-version>2</release-version>
-                        <specification-version>1.59</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.awt</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.76</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.filesystems</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
@@ -95,43 +70,11 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.loaders</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.76</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.modules</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.56</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.nodes</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>7.53</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.text</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>6.75</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
                     <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.15</specification-version>
+                        <specification-version>9.19</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
@@ -142,22 +85,6 @@
                         <specification-version>8.41</specification-version>
                     </run-dependency>
                 </dependency>
-                <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>9.16</specification-version>
-                    </run-dependency>
-                </dependency>
-                <dependency>
-                    <code-name-base>org.openide.windows</code-name-base>
-                    <build-prerequisite/>
-                    <compile-dependency/>
-                    <run-dependency>
-                        <specification-version>6.85</specification-version>
-                    </run-dependency>
-                </dependency>
             </module-dependencies>
             <test-dependencies>
                 <test-type>
@@ -186,13 +113,16 @@
                 </test-type>
             </test-dependencies>
             <friend-packages>
-                <friend>org.netbeans.modules.cpplite.project</friend>
-                <package>org.netbeans.modules.cpplite.debugger.api</package>
+                <friend>org.netbeans.modules.cpplite.debugger</friend>
+                <friend>org.netbeans.modules.gradle.java</friend>
+                <friend>org.netbeans.modules.java.lsp.server</friend>
+                <friend>org.netbeans.modules.java.nativeimage.debugger</friend>
+                <package>org.netbeans.modules.nativeimage.api</package>
+                <package>org.netbeans.modules.nativeimage.api.debug</package>
+                <package>org.netbeans.modules.nativeimage.spi</package>
+                <package>org.netbeans.modules.nativeimage.spi.debug</package>
+                <package>org.netbeans.modules.nativeimage.spi.debug.filters</package>
             </friend-packages>
-            <class-path-extension>
-                <runtime-relative-path>ext/cpplite-mi-f8f8250283be.jar</runtime-relative-path>
-                <binary-origin>external/cpplite-mi-f8f8250283be.jar</binary-origin>
-            </class-path-extension>
         </data>
     </configuration>
 </project>
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Bundle.properties b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Bundle.properties
new file mode 100644
index 0000000..139a1c1
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/Bundle.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# manifest's description entries
+OpenIDE-Module-Display-Category=Base IDE
+OpenIDE-Module-Name=Native Image API
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java
similarity index 66%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
copy to ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java
index 0fc9c76..1d542bb 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java
@@ -16,17 +16,35 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.nativeimage.api.debug;
 
-public final class EvaluateException extends Exception {
+/**
+ * Thrown when evaluation in the native debugger fails.
+ *
+ * @since 1.0
+ */
+public class EvaluateException extends Exception {
 
     /**
      * Constructs an instance of <code>EvaluateException</code> with the
      * specified detail message.
      *
      * @param msg the detail message.
+     * @since 1.0
      */
-    EvaluateException(String msg) {
+    public EvaluateException(String msg) {
         super(msg);
     }
+
+    /**
+     * Constructs an instance of <code>EvaluateException</code> from an
+     * existing exception.
+     *
+     * @param t exception.
+     * @since 1.0
+     */
+    public EvaluateException(Throwable t) {
+        super(t.getLocalizedMessage(), t);
+        
+    }
 }
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java
new file mode 100644
index 0000000..f980655
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.api.debug;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerEngine;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerServiceProvider;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.openide.util.Lookup;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+
+/**
+ * Representation of a native image debugger.
+ * @since 1.0
+ */
+public final class NIDebugger {
+
+    private final NIDebuggerProvider provider;
+
+    NIDebugger(NIDebuggerProvider provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * Creates a builder of a new debugger instance.
+     *
+     * @return a builder of a new debugger instance
+     * @throws IllegalStateException when the native debugger is not available
+     *         (there is not an implementation of {@link NIDebuggerServiceProvider}
+     *         registered in the default lookup).
+     * @since 1.0
+     */
+    @NbBundle.Messages({"MSG_NoNativeDebug=No native debugger is available. Please install native debugger module."})
+    public static Builder newBuilder() throws IllegalStateException {
+        NIDebuggerServiceProvider provider = Lookup.getDefault().lookup(NIDebuggerServiceProvider.class);
+        if (provider == null) {
+            throw Exceptions.attachLocalizedMessage(new IllegalStateException(), Bundle.MSG_NoNativeDebug());
+        } else {
+            return new Builder(provider.create());
+        }
+    }
+
+    /**
+     * Add or change a line breakpoint into the debugger.
+     * A breakpoint is added when the `id` is used for the first time and modified
+     * when breakpoint with that `id` was added already.
+     *
+     * @param id a unique ID of the breakpoint
+     * @param breakpointDescriptor the breakpoint descriptor
+     * @return an instance of the native breakpoint
+     * @since 1.0
+     */
+    public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor) {
+        Breakpoint breakpoint = provider.addLineBreakpoint(id, breakpointDescriptor);
+        assert breakpoint != null;
+        return breakpoint;
+    }
+
+    /**
+     * Remove breakpoint with the given id.
+     *
+     * @param id the ID of the breakpoint to remove
+     * @since 1.0
+     */
+    public void removeBreakpoint(Object id) {
+        provider.removeBreakpoint(id);
+    }
+
+    /**
+     * Start the actual debugging session. Call this typically after breakpoints are added.
+     *
+     * @param command a command to run the native image
+     * @param workingDirectory working directory
+     * @param debugger the native debugger command
+     * @param displayName display name of the debugger task
+     * @param executionDescriptor execution descriptor that describes the runtime attributes
+     * @param startedEngine the corresponding DebuggerEngine is passed to this consumer
+     * @return future that completes on the execution finish
+     * @since 1.0
+     */
+    public CompletableFuture<Void> start(List<String> command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine) {
+        return provider.start(command, workingDirectory, debugger, displayName, executionDescriptor, startedEngine);
+    }
+
+    /**
+     * An asynchronous expression evaluation.
+     *
+     * @param expression the expression to evaluate
+     * @param resultName preferred name of the result variable,
+     *                   when <code>null</code> the expression is used as the name
+     * @param frame the frame to evaluate at
+     * @return the completable future with the evaluation result
+     * @since 1.0
+     */
+    public CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName, NIFrame frame) {
+        return provider.evaluateAsync(expression, resultName, frame);
+    }
+
+    /**
+     * A synchronous expression evaluation. Delegates to the asynchronous evaluation
+     * and wait for it's result.
+     *
+     * @param expression the expression to evaluate
+     * @param resultName preferred name of the result variable,
+     *                   when <code>null</code> the expression is used as the name
+     * @param frame the frame to evaluate at
+     * @return the evaluation result
+     * @throws EvaluateException when evaluation fails
+     * @since 1.0
+     */
+    public NIVariable evaluate(String expression, String resultName, NIFrame frame) throws EvaluateException {
+        try {
+            return provider.evaluateAsync(expression, resultName, frame).get();
+        } catch (ExecutionException | InterruptedException ex) {
+            throw new EvaluateException(ex.getCause());
+        }
+    }
+
+    /**
+     * Read data from memory.
+     *
+     * @param address address where to read the data from
+     * @param offset offset relative to the address where to start reading
+     * @param length number of bytes to read
+     * @return hexadecimal representation of the memory content, or <code>null</code>
+     *         when the read is not successful
+     * @since 1.0
+     */
+    public String readMemory(String address, long offset, int length) {
+        return provider.readMemory(address, offset, length);
+    }
+
+    /**
+     * Get version of the underlying native debugger.
+     *
+     * @since 1.0
+     */
+    public String getVersion() {
+        return provider.getVersion();
+    }
+
+    /**
+     * A builder that creates a Native Image debugger with optional displayers.
+     *
+     * @since 1.0
+     */
+    public static final class Builder {
+
+        private final NIDebuggerProvider debuggerProvider;
+
+        Builder(NIDebuggerProvider debuggerProvider) {
+            this.debuggerProvider = debuggerProvider;
+        }
+
+        /**
+         * Displayer of native frames.
+         *
+         * @param frameDisplayer translator of the native frame to it's displayed information
+         * @since 1.0
+         */
+        public Builder frameDisplayer(FrameDisplayer frameDisplayer) {
+            this.debuggerProvider.setFrameDisplayer(frameDisplayer);
+            return this;
+        }
+
+        /**
+         * Displayer of native variables.
+         *
+         * @param variablesDisplayer translator of native variables to displayed variables.
+         * @since 1.0
+         */
+        public Builder variablesDisplayer(VariableDisplayer variablesDisplayer) {
+            this.debuggerProvider.setVariablesDisplayer(variablesDisplayer);
+            return this;
+        }
+
+        /**
+         * Create the debugger instance.
+         * @since 1.0
+         */
+        public NIDebugger build() {
+            return new NIDebugger(debuggerProvider);
+        }
+    }
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java
new file mode 100644
index 0000000..4ffc48c
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.api.debug;
+
+/**
+ * Representation of a native stack frame.
+ *
+ * @since 1.0
+ */
+public interface NIFrame {
+
+    /**
+     * Frame's thread ID.
+     * @since 1.0
+     */
+    String getThreadId();
+
+    /**
+     * Frame's depth level. The top frame has level 0.
+     * @since 1.0
+     */
+    int getLevel();
+
+    /**
+     * Frame's native address.
+     * @since 1.0
+     */
+    String getAddress();
+
+    /**
+     * A short name of the file associated with the frame.
+     * @since 1.0
+     */
+    String getShortFileName();
+
+    /**
+     * A full name of the file associated with the frame.
+     * @since 1.0
+     */
+    String getFullFileName();
+
+    /**
+     * Name of the function associated with the frame.
+     * @since 1.0
+     */
+    String getFunctionName();
+
+    /**
+     * 1-based line of the frame location.
+     * @since 1.0
+     */
+    int getLine();
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java
new file mode 100644
index 0000000..192be78
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.api.debug;
+
+import org.netbeans.api.annotations.common.CheckForNull;
+
+/**
+ * Description of a line native breakpoint.
+ *
+ * @since 1.0
+ */
+public final class NILineBreakpointDescriptor {
+
+    private final String filePath;
+    private final int line;
+    private final boolean enabled;
+    private final String condition;
+    private final boolean hidden;
+
+    private NILineBreakpointDescriptor(String filePath, int line, boolean enabled, String condition, boolean hidden) {
+        this.filePath = filePath;
+        this.line = line;
+        this.enabled = enabled;
+        this.condition = condition;
+        this.hidden = hidden;
+    }
+
+    /**
+     * Create a new line native breakpoint builder.
+     *
+     * @param filePath file path of the breakpoint
+     * @param lineNumber 1-based line number
+     * @since 1.0
+     */
+    public static Builder newBuilder(String filePath, int lineNumber) {
+        return new Builder(filePath, lineNumber);
+    }
+
+    /**
+     * Get path of the file.
+     *
+     * @since 1.0
+     */
+    public String getFilePath() {
+        return filePath;
+    }
+
+    /**
+     * Get 1-based line number.
+     *
+     * @since 1.0
+     */
+    public int getLine() {
+        return line;
+    }
+
+    /**
+     * Check if the breakpoint is to be enabled.
+     *
+     * @since 1.0
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Get the breakpoint condition.
+     *
+     * @return the condition, or <code>null</code> when the breakpoint does not have any condition.
+     * @since 1.0
+     */
+    @CheckForNull
+    public String getCondition() {
+        return condition;
+    }
+
+    /**
+     * Check if the breakpoint is to be hidden (not user-visible).
+     *
+     * @since 1.0
+     */
+    public boolean isHidden() {
+        return hidden;
+    }
+
+    /**
+     * Builder of a line native breakpoint descriptor. The builder is reusable
+     * and the built breakpoint descriptor can be used to update existing breakpoints.
+     *
+     * @since 1.0
+     */
+    public static final class Builder {
+
+        private String filePath;
+        private int line;
+        private boolean enabled = true;
+        private String condition;
+        private boolean hidden = false;
+
+        Builder(String filePath, int lineNumber) {
+            this.filePath = filePath;
+            this.line = lineNumber;
+        }
+
+        /**
+         * Set a file path.
+         *
+         * @since 1.0
+         */
+        public Builder filePath(String filePath) {
+            this.filePath = filePath;
+            return this;
+        }
+
+        /**
+         * Set a 1-based line number.
+         *
+         * @since 1.0
+         */
+        public Builder line(int lineNumber) {
+            this.line = lineNumber;
+            return this;
+        }
+
+        /**
+         * Set a condition.
+         *
+         * @since 1.0
+         */
+        public Builder condition(String condition) {
+            this.condition = condition;
+            return this;
+        }
+
+        /**
+         * Set an enabled state of the breakpoint. The breakpoint is enabled by default.
+         *
+         * @since 1.0
+         */
+        public Builder enabled(boolean enabled) {
+            this.enabled = enabled;
+            return this;
+        }
+
+        /**
+         * Set a hidden state of the breakpoint. Hidden breakpoints are not visible
+         * to users. The breakpoint is not hidden by default.
+         *
+         * @since 1.0
+         */
+        public Builder hidden(boolean hidden) {
+            this.hidden = hidden;
+            return this;
+        }
+
+        /**
+         * Build the line breakpoint descriptor.
+         */
+        public NILineBreakpointDescriptor build() {
+            return new NILineBreakpointDescriptor(filePath, line, enabled, condition, hidden);
+        }
+    }
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java
new file mode 100644
index 0000000..cd46e7c
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.api.debug;
+
+/**
+ * Representation of a native variable.
+ * @since 1.0
+ */
+public interface NIVariable {
+
+    /**
+     * Name of the variable.
+     *
+     * @since 1.0
+     */
+    String getName();
+
+    /**
+     * Type of the variable value.
+     *
+     * @since 1.0
+     */
+    String getType();
+
+    /**
+     * String representation of the variable value.
+     *
+     * @since 1.0
+     */
+    String getValue();
+
+    /**
+     * The parent variable, if any. Every child variable has a corresponding parent
+     * variable.
+     *
+     * @return the parent variable, or <code>null</code>.
+     * @since 1.0
+     */
+    NIVariable getParent();
+
+    /**
+     * Number of child variables (properties, or array elements) of this variable.
+     *
+     * @since 1.0
+     */
+    int getNumChildren();
+
+    /**
+     * Get the child variables in the specified index range. Children starting at
+     * <code>from</code> index and up to and excluding <code>to</code> index will
+     * be returned. If <code>from</code> is less than zero, all children are returned.
+     *
+     * @since 1.0
+     */
+    NIVariable[] getChildren(int from, int to);
+
+    /**
+     * Get all variable's children.
+     *
+     * @since 1.0
+     */
+    default NIVariable[] getChildren() {
+        return getChildren(0, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Get the full expression that this variable object represents.
+     *
+     * @since 1.0
+     */
+    String getExpressionPath();
+
+    /**
+     * Get the frame this variable is associated with.
+     *
+     * @return the frame, or <code>null</code>.
+     * @since 1.0
+     */
+    NIFrame getFrame();
+}
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java
new file mode 100644
index 0000000..c2ff425
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.spi.debug;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerEngine;
+
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+
+/**
+ * Provider of the native image debugger.
+ *
+ * @author martin
+ * @since 1.0
+ */
+public interface NIDebuggerProvider {
+
+    /**
+     * Add or change a line breakpoint into the debugger.
+     * A breakpoint is added when the `id` is used for the first time and modified
+     * when breakpoint with that `id` was added already.
+     *
+     * @param id a unique ID of the breakpoint
+     * @param breakpointDescriptor the breakpoint descriptor
+     * @return an instance of the native breakpoint
+     * @since 1.0
+     */
+    Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor);
+
+    /**
+     * Remove breakpoint with the given id.
+     *
+     * @param id the ID of the breakpoint to remove
+     * @since 1.0
+     */
+    void removeBreakpoint(Object id);
+
+    /**
+     * Set a displayer of native frames.
+     *
+     * @param frameDisplayer translator of the native frame to it's displayed information
+     * @since 1.0
+     */
+    void setFrameDisplayer(FrameDisplayer frameDisplayer);
+
+    /**
+     * Set a displayer of native variables.
+     *
+     * @param variablesDisplayer translator of native variables to displayed variables.
+     * @since 1.0
+     */
+    void setVariablesDisplayer(VariableDisplayer variablesDisplayer);
+
+    /**
+     * Start the actual debugging session. Called typically after breakpoints are added.
+     *
+     * @param command a command to run the native image
+     * @param workingDirectory working directory
+     * @param debugger the native debugger command
+     * @param displayName display name of the debugger task
+     * @param executionDescriptor execution descriptor that describes the runtime attributes
+     * @param startedEngine the corresponding DebuggerEngine is passed to this consumer
+     * @param finishedCallback notification of the execution finish
+     * @since 1.0
+     */
+    CompletableFuture<Void> start(List<String> command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine);
+
+    /**
+     * An asynchronous expression evaluation.
+     *
+     * @param expression the expression to evaluate
+     * @param resultName preferred name of the result variable,
+     *                   when <code>null</code> the expression is used as the name
+     * @param frame the frame to evaluate at
+     * @return the completable future with the evaluation result
+     * @since 1.0
+     */
+    CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName, NIFrame frame);
+
+    /**
+     * Read data from memory.
+     *
+     * @param address address where to read the data from
+     * @param offset offset relative to the address where to start reading
+     * @param length number of bytes to read
+     * @return hexadecimal representation of the memory content, or <code>null</code>
+     *         when the read is not successful
+     * @since 1.0
+     */
+    String readMemory(String address, long offset, int length);
+
+    /**
+     * Get version of the underlying native debugger.
+     *
+     * @since 1.0
+     */
+    String getVersion();
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerServiceProvider.java
similarity index 72%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
copy to ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerServiceProvider.java
index 0fc9c76..37ec9fe 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerServiceProvider.java
@@ -16,17 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.nativeimage.spi.debug;
 
-public final class EvaluateException extends Exception {
+/**
+ * Provider of the native image debugger.
+ * @since 1.0
+ */
+public interface NIDebuggerServiceProvider {
 
     /**
-     * Constructs an instance of <code>EvaluateException</code> with the
-     * specified detail message.
-     *
-     * @param msg the detail message.
+     * Create a new instance of the debugger provider.
+     * @since 1.0
      */
-    EvaluateException(String msg) {
-        super(msg);
-    }
+    NIDebuggerProvider create();
 }
diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/FrameDisplayer.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/FrameDisplayer.java
new file mode 100644
index 0000000..91d92cc
--- /dev/null
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/FrameDisplayer.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.spi.debug.filters;
+
+import java.net.URI;
+import java.util.function.Supplier;
+import org.netbeans.api.annotations.common.CheckForNull;
+
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+
+/**
+ * Displayer of stack frames. Modifies the way how frames are presented.
+ *
+ * @since 1.0
+ */
+public interface FrameDisplayer {
+
+    /**
+     * Provide display information of a stack frame.
+     *
+     * @param frame the stack frame
+     * @return a display information, or <code>null</code> to skip this frame.
+     * @since 1.0
+     */
+    DisplayedFrame displayed(NIFrame frame);
+
+    /**
+     * Display information of a frame.
+     * @since 1.0
+     */
+    public static final class DisplayedFrame {
+
+        private final String displayName;
+        private final String description;
+        private final int line;
+        private final Supplier<URI> uriSupplier;
+
+        private DisplayedFrame(String displayName, String description, int line, Supplier<URI> uriSupplier) {
+            this.displayName = displayName;
+            this.description = description;
+            this.line = line;
+            this.uriSupplier = uriSupplier;
+        }
+
+        /**
+         * Creates a new builder with a user visible display name of the frame.
+         *
+         * @since 1.0
+         */
+        public static Builder newBuilder(String displayName) {
+            return new Builder(displayName);
+        }
+
+        /**
+         * Get a display name of the frame.
+         *
+         * @since 1.0
+         */
+        public String getDisplayName() {
+            return displayName;
+        }
+
+        /**
+         * Get a description of the frame.
+         *
+         * @since 1.0
+         */
+        public String getDescription() {
+            return description;
+        }
+
+        /**
+         * Get a 1-based line number of the frame.
+         *
+         * @since 1.0
+         */
+        public int getLine() {
+            return line;
+        }
+
+        /**
+         * Get URI of the source file associated with the frame.
+         *
+         * @return the URI, or <code>null</code> when unknown.
+         * @since 1.0
+         */
+        @CheckForNull
+        public URI getSourceURI() {
+            if (uriSupplier != null) {
+                return uriSupplier.get();
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Builder of the {@link DisplayedFrame}.
+         * @since 1.0
+         */
+        public static final class Builder {
+
+            private final String displayName;
+            private String description;
+            private int line;
+            private Supplier<URI> uriSupplier;
+
+            Builder(String displayName) {
+                this.displayName = displayName;
+            }
+
+            /**
+             * Set frame description.
+             *
+             * @since 1.0
+             */
+            public Builder description(String description) {
+                this.description = description;
+                return this;
+            }
+
+            /**
+             * 1-based line number information of the frame location.
+             *
+             * @since 1.0
+             */
+            public Builder line(int lineNumber) {
+                this.line = lineNumber;
+                return this;
+            }
+
+            /**
+             * URI of the source file associated with the frame.
+             * @since 1.0
+             */
+            public Builder sourceURISupplier(Supplier<URI> uriSupplier) {
+                this.uriSupplier = uriSupplier;
+                return this;
+            }
+
+            /**
+             * Build the {@link DisplayedFrame}.
+             *
+             * @since 1.0
+             */
+            public DisplayedFrame build() {
+                return new DisplayedFrame(displayName, description, line, uriSupplier);
+            }
+        }
+    }
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/VariableDisplayer.java
similarity index 64%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
copy to ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/VariableDisplayer.java
index 0fc9c76..9d6cfa1 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/filters/VariableDisplayer.java
@@ -16,17 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.nativeimage.spi.debug.filters;
 
-public final class EvaluateException extends Exception {
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+
+/**
+ * Displayer of variables. Modifies the way how variables are presented.
+ *
+ * @since 1.0
+ */
+public interface VariableDisplayer {
 
     /**
-     * Constructs an instance of <code>EvaluateException</code> with the
-     * specified detail message.
-     *
-     * @param msg the detail message.
+     * Translate a list of original native variables into a list of variables
+     * presented to the debugger user.
+     * @since 1.0
      */
-    EvaluateException(String msg) {
-        super(msg);
-    }
+    NIVariable[] displayed(NIVariable... variables);
+
 }
diff --git a/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/NIDebuggerServiceTest.java b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/NIDebuggerServiceTest.java
new file mode 100644
index 0000000..f7ad3dd
--- /dev/null
+++ b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/NIDebuggerServiceTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.debug;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
+
+public class NIDebuggerServiceTest {
+
+    public NIDebuggerServiceTest() {
+    }
+
+    private static Lookup getTestLookup(String checkStartParams) {
+        Lookup launchCtx = new ProxyLookup(
+                        Lookups.fixed(new TestNIDebuggerServiceProvider(checkStartParams)),
+                        Lookup.getDefault()
+                );
+        return launchCtx;
+    }
+
+    @Test
+    public void testFindServiceProvider() {
+        Lookups.executeWith(getTestLookup(""), () -> {
+            NIDebugger debugger = NIDebugger.newBuilder().build();
+            assertNotNull("Test NI debugger service is not available.", debugger);
+        });
+    }
+
+    @Test
+    public void testDebuggerProvider() {
+        String checkStartParams = "[CMD1, CMD2]WDMIDdisplayNamenull{ID1=TEST ID1filePath110truecondition1false}nullnull";
+        Lookups.executeWith(getTestLookup(checkStartParams), () -> {
+            NIDebugger debugger = NIDebugger.newBuilder().build();
+            debugger.addLineBreakpoint("ID1", NILineBreakpointDescriptor.newBuilder("filePath1", 10).enabled(true).condition("condition1").hidden(false).build());
+            debugger.addLineBreakpoint("ID2", NILineBreakpointDescriptor.newBuilder("filePath2", 20).enabled(false).condition("condition2").hidden(true).build());
+            debugger.removeBreakpoint("ID2");
+            CompletableFuture<Void> completed = debugger.start(Arrays.asList("CMD1", "CMD2"), new File("WD"), "MID", "displayName", null, engine -> {});
+            assertTrue(completed.isDone());
+        });
+    }
+}
diff --git a/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerProvider.java b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerProvider.java
new file mode 100644
index 0000000..309b859
--- /dev/null
+++ b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerProvider.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.nativeimage.debug;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import static org.junit.Assert.assertEquals;
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerEngine;
+
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+
+public class TestNIDebuggerProvider implements NIDebuggerProvider {
+
+    private final Map<Object, String> breakpoints = new HashMap<>();
+    private final String checkStartParams;
+    private FrameDisplayer frameDisplayer;
+    private VariableDisplayer variablesDisplayer;
+
+    public TestNIDebuggerProvider(String checkStartParams) {
+        this.checkStartParams = checkStartParams;
+    }
+
+    @Override
+    public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor bd) {
+        String testBP = id + bd.getFilePath() + bd.getLine() + bd.isEnabled() + bd.getCondition() + bd.isHidden();
+        breakpoints.put(id, testBP);
+        return new Breakpoint() {
+            @Override
+            public boolean isEnabled() {
+                return true;
+            }
+            @Override
+            public void disable() {}
+            @Override
+            public void enable() {}
+        };
+    }
+
+    @Override
+    public void removeBreakpoint(Object id) {
+        breakpoints.remove(id);
+    }
+
+    @Override
+    public void setFrameDisplayer(FrameDisplayer frameDisplayer) {
+        this.frameDisplayer = frameDisplayer;
+    }
+
+    @Override
+    public void setVariablesDisplayer(VariableDisplayer variablesDisplayer) {
+        this.variablesDisplayer = variablesDisplayer;
+    }
+
+    @Override
+    public CompletableFuture<Void> start(List<String> command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine) {
+        assertEquals(checkStartParams, command.toString() + workingDirectory + debugger + displayName + executionDescriptor + breakpoints.toString() + frameDisplayer + variablesDisplayer);
+        startedEngine.accept(null);
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName, NIFrame frame) {
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public String readMemory(String address, long offset, int length) {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public String getVersion() {
+        return "Test_NI";
+    }
+
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerServiceProvider.java
similarity index 59%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
copy to ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerServiceProvider.java
index 0fc9c76..7a16f19 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/ide/nativeimage.api/test/unit/src/org/netbeans/modules/nativeimage/debug/TestNIDebuggerServiceProvider.java
@@ -16,17 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.nativeimage.debug;
 
-public final class EvaluateException extends Exception {
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerServiceProvider;
 
-    /**
-     * Constructs an instance of <code>EvaluateException</code> with the
-     * specified detail message.
-     *
-     * @param msg the detail message.
-     */
-    EvaluateException(String msg) {
-        super(msg);
+public class TestNIDebuggerServiceProvider implements NIDebuggerServiceProvider {
+
+    private final String checkStartParams;
+
+    public TestNIDebuggerServiceProvider(String checkStartParams) {
+        this.checkStartParams = checkStartParams;
     }
+
+    @Override
+    public NIDebuggerProvider create() {
+        return new TestNIDebuggerProvider(checkStartParams);
+    }
+
 }
diff --git a/java/java.kit/nbproject/project.xml b/java/java.kit/nbproject/project.xml
index 51b43fc..607df4c 100644
--- a/java/java.kit/nbproject/project.xml
+++ b/java/java.kit/nbproject/project.xml
@@ -167,6 +167,13 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.java.nativeimage.debugger</code-name-base>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.java.navigation</code-name-base>
                     <run-dependency>
                         <release-version>1</release-version>
diff --git a/java/java.lsp.server/nbcode/nbproject/platform.properties b/java/java.lsp.server/nbcode/nbproject/platform.properties
index a4903f4..9e0f111 100644
--- a/java/java.lsp.server/nbcode/nbproject/platform.properties
+++ b/java/java.lsp.server/nbcode/nbproject/platform.properties
@@ -17,6 +17,7 @@
 
 branding.token=nbcode
 cluster.path=\
+    ${nbplatform.active.dir}/cpplite:\
     ${nbplatform.active.dir}/enterprise:\
     ${nbplatform.active.dir}/extide:\
     ${nbplatform.active.dir}/ide:\
@@ -30,8 +31,6 @@ disabled.modules=\
     bcprov,\
     com.google.guava,\
     com.googlecode.javaewah.JavaEWAH,\
-    com.jcraft.jsch,\
-    com.jcraft.jzlib,\
     libs.c.kohlschutter.junixsocket,\
     org.apache.commons.httpclient,\
     org.apache.commons.lang,\
@@ -110,6 +109,9 @@ disabled.modules=\
     org.netbeans.modules.cordova.platforms,\
     org.netbeans.modules.cordova.platforms.android,\
     org.netbeans.modules.core.kit,\
+    org.netbeans.modules.cpplite.editor,\
+    org.netbeans.modules.cpplite.kit,\
+    org.netbeans.modules.cpplite.project,\
     org.netbeans.modules.css.editor,\
     org.netbeans.modules.css.model,\
     org.netbeans.modules.css.prep,\
@@ -132,7 +134,6 @@ disabled.modules=\
     org.netbeans.modules.debugger.jpda.kit,\
     org.netbeans.modules.debugger.jpda.visual,\
     org.netbeans.modules.derby,\
-    org.netbeans.modules.dlight.nativeexecution,\
     org.netbeans.modules.dlight.nativeexecution.nb,\
     org.netbeans.modules.dlight.terminal,\
     org.netbeans.modules.docker.api,\
@@ -300,7 +301,6 @@ disabled.modules=\
     org.netbeans.modules.team.commons,\
     org.netbeans.modules.team.ide,\
     org.netbeans.modules.templates,\
-    org.netbeans.modules.terminal,\
     org.netbeans.modules.terminal.nb,\
     org.netbeans.modules.testng.ant,\
     org.netbeans.modules.testng.maven,\
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index d78ddd8..eb90897 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -321,6 +321,24 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
+                    <code-name-base>org.netbeans.modules.nativeimage.api</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.nativeimage.debugger</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
                     <code-name-base>org.netbeans.modules.parsing.api</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
diff --git a/java/java.lsp.server/script/etc/nbcode.clusters b/java/java.lsp.server/script/etc/nbcode.clusters
index 2151943..9a5a263 100644
--- a/java/java.lsp.server/script/etc/nbcode.clusters
+++ b/java/java.lsp.server/script/etc/nbcode.clusters
@@ -3,6 +3,7 @@ ide
 extide
 webcommon
 java
+cpplite
 extra
 enterprise
 nbcode
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
index 6b274d2..4541ed3 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbProtocolServer.java
@@ -29,6 +29,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.lsp4j.debug.Capabilities;
@@ -69,6 +71,7 @@ import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
 import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
 import org.netbeans.api.debugger.ActionsManager;
 import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.debugger.Session;
 import org.netbeans.api.debugger.jpda.InvalidExpressionException;
 import org.netbeans.api.debugger.jpda.JPDADebugger;
 import org.netbeans.api.debugger.jpda.ObjectVariable;
@@ -80,6 +83,9 @@ import org.netbeans.modules.java.lsp.server.debugging.launch.NbLaunchRequestHand
 import org.netbeans.modules.java.lsp.server.debugging.breakpoints.NbBreakpointsRequestHandler;
 import org.netbeans.modules.java.lsp.server.debugging.variables.NbVariablesRequestHandler;
 import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
 import org.netbeans.spi.debugger.ui.DebuggingView.DVFrame;
 import org.netbeans.spi.debugger.ui.DebuggingView.DVThread;
 
@@ -187,8 +193,8 @@ public final class NbProtocolServer implements IDebugProtocolServer {
             }
             response.setAllThreadsContinued(false);
         } else {
-            JPDADebugger debugger = context.getDebugSession().getDebugger();
-            debugger.getSession().getCurrentEngine().getActionsManager().doAction("continue");
+            Session session = context.getDebugSession().getSession();
+            session.getCurrentEngine().getActionsManager().doAction("continue");
             context.getThreadsProvider().getThreadObjects().cleanAll();
             response.setAllThreadsContinued(true);
         }
@@ -249,8 +255,8 @@ public final class NbProtocolServer implements IDebugProtocolServer {
                 ev = null;
             }
         } else {
-            JPDADebugger debugger = context.getDebugSession().getDebugger();
-            debugger.getSession().getCurrentEngine().getActionsManager().doAction("pause");
+            Session session = context.getDebugSession().getSession();
+            session.getCurrentEngine().getActionsManager().doAction("pause");
             ev = new StoppedEventArguments();
             ev.setReason("pause");
             ev.setThreadId(0);
@@ -417,48 +423,78 @@ public final class NbProtocolServer implements IDebugProtocolServer {
             stackFrame.getDVFrame().makeCurrent(); // The evaluation is always performed with respect to the current frame
             DVThread dvThread = stackFrame.getDVFrame().getThread();
             int threadId = context.getThreadsProvider().getId(dvThread);
-            JPDADebugger debugger = context.getDebugSession().getDebugger();
-            Variable variable;
-            try {
-                variable = debugger.evaluate(expression);
-            } catch (InvalidExpressionException ex) {
-                throw ErrorUtilities.createResponseErrorException(
-                    ex.getLocalizedMessage(),
-                    ResponseErrorCode.ParseError);
-            }
             EvaluateResponse response = new EvaluateResponse();
-            TruffleVariable truffleVariable = TruffleVariable.get(variable);
-            if (truffleVariable != null) {
-                int referenceId = context.getThreadsProvider().getThreadObjects().addObject(threadId, truffleVariable);
-                response.setResult(truffleVariable.getDisplayValue());
-                response.setVariablesReference(referenceId);
-                response.setType(truffleVariable.getType());
-                response.setIndexedVariables(truffleVariable.isLeaf() ? 0 : truffleVariable.getChildren().length);
+            JPDADebugger debugger = context.getDebugSession().getJPDADebugger();
+            if (debugger != null) {
+                evaluateJPDA(debugger, expression, threadId, response);
             } else {
-                if (variable instanceof ObjectVariable) {
-                    int referenceId = context.getThreadsProvider().getThreadObjects().addObject(threadId, variable);
-                    int indexedVariables = ((ObjectVariable) variable).getFieldsCount();
-                    String toString;
-                    try {
-                        toString = ((ObjectVariable) variable).getToStringValue();
-                    } catch (InvalidExpressionException ex) {
-                        toString = variable.getValue();
-                    }
-                    response.setResult(toString);
-                    response.setVariablesReference(referenceId);
-                    response.setType(variable.getType());
-                    response.setIndexedVariables(Math.max(indexedVariables, 0));
-                } else {
-                    response.setResult(variable.getValue());
-                    response.setVariablesReference(0);
-                    response.setType(variable.getType());
-                    response.setIndexedVariables(0);
-                }
+                NIDebugger niDebugger = context.getDebugSession().getNIDebugger();
+                evaluateNative(niDebugger, expression, threadId, response);
             }
             return response;
         });
     }
 
+    private void evaluateJPDA(JPDADebugger debugger, String expression, int threadId, EvaluateResponse response) {
+        Variable variable;
+        try {
+            variable = debugger.evaluate(expression);
+        } catch (InvalidExpressionException ex) {
+            throw ErrorUtilities.createResponseErrorException(
+                ex.getLocalizedMessage(),
+                ResponseErrorCode.ParseError);
+        }
+        TruffleVariable truffleVariable = TruffleVariable.get(variable);
+        if (truffleVariable != null) {
+            int referenceId = context.getThreadsProvider().getThreadObjects().addObject(threadId, truffleVariable);
+            response.setResult(truffleVariable.getDisplayValue());
+            response.setVariablesReference(referenceId);
+            response.setType(truffleVariable.getType());
+            response.setIndexedVariables(truffleVariable.isLeaf() ? 0 : truffleVariable.getChildren().length);
+        } else {
+            if (variable instanceof ObjectVariable) {
+                int referenceId = context.getThreadsProvider().getThreadObjects().addObject(threadId, variable);
+                int indexedVariables = ((ObjectVariable) variable).getFieldsCount();
+                String toString;
+                try {
+                    toString = ((ObjectVariable) variable).getToStringValue();
+                } catch (InvalidExpressionException ex) {
+                    toString = variable.getValue();
+                }
+                response.setResult(toString);
+                response.setVariablesReference(referenceId);
+                response.setType(variable.getType());
+                response.setIndexedVariables(Math.max(indexedVariables, 0));
+            } else {
+                response.setResult(variable.getValue());
+                //response.setVariablesReference(0);
+                response.setType(variable.getType());
+                //response.setIndexedVariables(0);
+            }
+        }
+    }
+
+    private void evaluateNative(NIDebugger niDebugger, String expression, int threadId, EvaluateResponse response) {
+        try {
+            NIVariable variable = niDebugger.evaluate(expression, null, null);
+            int numChildren = variable.getNumChildren();
+            if (numChildren > 0) {
+                int referenceId = context.getThreadsProvider().getThreadObjects().addObject(threadId, variable);
+                response.setResult(variable.getValue());
+                response.setVariablesReference(referenceId);
+                response.setType(variable.getType());
+                response.setIndexedVariables(numChildren);
+            } else {
+                response.setResult(variable.getValue());
+                response.setType(variable.getType());
+            }
+        } catch (EvaluateException ex) {
+            throw ErrorUtilities.createResponseErrorException(
+                ex.getLocalizedMessage(),
+                ResponseErrorCode.ParseError);
+        }
+    }
+
     @Override
     public CompletableFuture<ExceptionInfoResponse> exceptionInfo(ExceptionInfoArguments args) {
         CompletableFuture<ExceptionInfoResponse> future = new CompletableFuture<>();
@@ -466,7 +502,6 @@ public final class NbProtocolServer implements IDebugProtocolServer {
         if (exceptionVariable == null) {
             ErrorUtilities.completeExceptionally(future, "No exception exists in thread " + args.getThreadId(), ResponseErrorCode.InvalidParams);
         } else {
-            JPDADebugger debugger = context.getDebugSession().getDebugger();
             Throwable exception = (Throwable) exceptionVariable.createMirrorObject();
             String typeName = exception.getLocalizedMessage(); // TODO
             String exceptionToString = exception.toString();
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbThreads.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbThreads.java
index 4e7e439..96c5f79 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbThreads.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/NbThreads.java
@@ -55,28 +55,27 @@ public final class NbThreads {
             @Override
             public void sessionAdded(Session session) {
                 DebuggerManager.getDebuggerManager().removeDebuggerListener(DebuggerManager.PROP_SESSIONS, this);
-                JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
-                initThreads(context, debugger);
+                initThreads(context, session);
             }
         });
     }
 
-    private void initThreads(DebugAdapterContext context, JPDADebugger debugger) {
-        DebuggerEngine engine = debugger.getSession().getCurrentEngine();
+    private void initThreads(DebugAdapterContext context, Session session) {
+        DebuggerEngine engine = session.getCurrentEngine();
         if (engine == null) {
-            debugger.getSession().addPropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, new PropertyChangeListener() {
+            session.addPropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, new PropertyChangeListener() {
                 @Override
                 public void propertyChange(PropertyChangeEvent evt) {
-                    DebuggerEngine currentEngine = debugger.getSession().getCurrentEngine();
+                    DebuggerEngine currentEngine = session.getCurrentEngine();
                     if (currentEngine != null) {
-                        debugger.getSession().removePropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, this);
+                        session.removePropertyChangeListener(Session.PROP_CURRENT_LANGUAGE, this);
                         if (!initialized.getAndSet(true)) {
                             initThreads(context, currentEngine);
                         }
                     }
                 }
             });
-            engine = debugger.getSession().getCurrentEngine();
+            engine = session.getCurrentEngine();
         }
         if (engine != null && !initialized.getAndSet(true)) {
             initThreads(context, engine);
@@ -209,7 +208,11 @@ public final class NbThreads {
 
     private JPDAThread getJPDAThread(DVThread dvThread) {
         // JPDA implementation implements Supplier.
-        return ((Supplier<JPDAThread>) dvThread).get();
+        if (dvThread instanceof Supplier) {
+            return ((Supplier<JPDAThread>) dvThread).get();
+        } else {
+            return null;
+        }
     }
 
     public void visitThreads(BiConsumer<Integer, DVThread> threadsConsumer) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
index 1167740..e18052c 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbDebugSession.java
@@ -18,7 +18,9 @@
  */
 package org.netbeans.modules.java.lsp.server.debugging.launch;
 
+import org.netbeans.api.debugger.Session;
 import org.netbeans.api.debugger.jpda.JPDADebugger;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
 
 /**
  *
@@ -26,14 +28,27 @@ import org.netbeans.api.debugger.jpda.JPDADebugger;
  */
 public final class NbDebugSession {
 
-    private final JPDADebugger debugger;
+    private final Session session;
+    private volatile NIDebugger niDebugger;
 
-    NbDebugSession(JPDADebugger debugger) {
-        this.debugger = debugger;
+    NbDebugSession(Session session) {
+        this.session = session;
     }
 
-    public JPDADebugger getDebugger() {
-        return debugger;
+    public Session getSession() {
+        return session;
+    }
+
+    public JPDADebugger getJPDADebugger() {
+        return session.lookupFirst(null, JPDADebugger.class);
+    }
+
+    public NIDebugger getNIDebugger() {
+        return niDebugger;
+    }
+
+    void setNIDebugger(NIDebugger niDebugger) {
+        this.niDebugger = niDebugger;
     }
 
     public void detach() {
@@ -41,7 +56,7 @@ public final class NbDebugSession {
     }
 
     public void terminate() {
-        debugger.getSession().kill();
+        session.kill();
     }
 
     public void setExceptionBreakpoints(boolean notifyCaught, boolean notifyUncaught) {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
index 789a072..a254307 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
@@ -20,6 +20,8 @@ package org.netbeans.modules.java.lsp.server.debugging.launch;
 
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -27,6 +29,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
@@ -40,6 +43,8 @@ import org.netbeans.api.debugger.DebuggerManagerAdapter;
 import org.netbeans.api.debugger.Session;
 import org.netbeans.api.debugger.jpda.JPDADebugger;
 import org.netbeans.api.extexecution.base.ExplicitProcessParameters;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.extexecution.ExecutionService;
 import org.netbeans.api.java.classpath.ClassPath;
 import org.netbeans.api.java.queries.UnitTestForSourceQuery;
 import org.netbeans.api.project.FileOwnerQuery;
@@ -48,10 +53,13 @@ import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.modules.java.lsp.server.Utils;
 import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
 import org.netbeans.modules.java.lsp.server.debugging.NbSourceProvider;
+import org.netbeans.modules.java.lsp.server.debugging.utils.ErrorUtilities;
 import org.netbeans.modules.java.lsp.server.progress.OperationContext;
 import org.netbeans.modules.java.lsp.server.progress.ProgressOperationEvent;
 import org.netbeans.modules.java.lsp.server.progress.ProgressOperationListener;
 import org.netbeans.modules.java.lsp.server.progress.TestProgressHandler;
+import org.netbeans.modules.java.nativeimage.debugger.api.NIDebugRunner;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
 import org.netbeans.spi.project.ActionProgress;
 import org.netbeans.spi.project.ActionProvider;
 import org.netbeans.spi.project.SingleMethod;
@@ -75,7 +83,7 @@ public abstract class NbLaunchDelegate {
         // no op.
     }
 
-    public final CompletableFuture<Void> nbLaunch(FileObject toRun, String method, Map<String, Object> launchArguments, DebugAdapterContext context, boolean debug, boolean testRun, Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
+    public final CompletableFuture<Void> nbLaunch(FileObject toRun, File nativeImageFile, String method, Map<String, Object> launchArguments, DebugAdapterContext context, boolean debug, boolean testRun, Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
         CompletableFuture<Void> launchFuture = new CompletableFuture<>();
         NbProcessConsole ioContext = new NbProcessConsole(consoleMessages);
         SingleMethod singleMethod;
@@ -84,88 +92,161 @@ public abstract class NbLaunchDelegate {
         } else {
             singleMethod = null;
         }
-        CompletableFuture<Pair<ActionProvider, String>> commandFuture = findTargetWithPossibleRebuild(toRun, singleMethod, debug, testRun, ioContext);
-        commandFuture.thenAccept((providerAndCommand) -> {
-            if (debug) {
-                DebuggerManager.getDebuggerManager().addDebuggerListener(new DebuggerManagerAdapter() {
-                    @Override
-                    public void sessionAdded(Session session) {
-                        JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
-                        if (debugger != null) {
-                            DebuggerManager.getDebuggerManager().removeDebuggerListener(this);
-                            Map properties = session.lookupFirst(null, Map.class);
-                            NbSourceProvider sourceProvider = context.getSourceProvider();
-                            sourceProvider.setSourcePath(properties != null ? (ClassPath) properties.getOrDefault("sourcepath", ClassPath.EMPTY) : ClassPath.EMPTY);
-                            debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, new PropertyChangeListener() {
-                                @Override
-                                public void propertyChange(PropertyChangeEvent evt) {
-                                    int newState = (int) evt.getNewValue();
-                                    if (newState == JPDADebugger.STATE_RUNNING) {
-                                        debugger.removePropertyChangeListener(JPDADebugger.PROP_STATE, this);
-                                        NbDebugSession debugSession = new NbDebugSession(debugger);
-                                        context.setDebugSession(debugSession);
-                                        launchFuture.complete(null);
-                                        context.getConfigurationSemaphore().waitForConfigurationDone();
+        ActionProgress progress = new ActionProgress() {
+            @Override
+            protected void started() {
+            }
+
+            @Override
+            public void finished(boolean success) {
+                ioContext.stop();
+                notifyFinished(context, success);
+            }
+        };
+        if (toRun != null) {
+            CompletableFuture<Pair<ActionProvider, String>> commandFuture = findTargetWithPossibleRebuild(toRun, singleMethod, debug, testRun, ioContext);
+            commandFuture.thenAccept((providerAndCommand) -> {
+                if (debug) {
+                    DebuggerManager.getDebuggerManager().addDebuggerListener(new DebuggerManagerAdapter() {
+                        @Override
+                        public void sessionAdded(Session session) {
+                            JPDADebugger debugger = session.lookupFirst(null, JPDADebugger.class);
+                            if (debugger != null) {
+                                DebuggerManager.getDebuggerManager().removeDebuggerListener(this);
+                                Map properties = session.lookupFirst(null, Map.class);
+                                NbSourceProvider sourceProvider = context.getSourceProvider();
+                                sourceProvider.setSourcePath(properties != null ? (ClassPath) properties.getOrDefault("sourcepath", ClassPath.EMPTY) : ClassPath.EMPTY);
+                                debugger.addPropertyChangeListener(JPDADebugger.PROP_STATE, new PropertyChangeListener() {
+                                    @Override
+                                    public void propertyChange(PropertyChangeEvent evt) {
+                                        int newState = (int) evt.getNewValue();
+                                        if (newState == JPDADebugger.STATE_RUNNING) {
+                                            debugger.removePropertyChangeListener(JPDADebugger.PROP_STATE, this);
+                                            NbDebugSession debugSession = new NbDebugSession(session);
+                                            context.setDebugSession(debugSession);
+                                            launchFuture.complete(null);
+                                            context.getConfigurationSemaphore().waitForConfigurationDone();
+                                        }
                                     }
-                                }
-                            });
+                                });
+                            }
                         }
+                    });
+                } else {
+                    launchFuture.complete(null);
+                }
+                List<String> args = argsToStringList(launchArguments.get("args"));
+                List<String> vmArgs = argsToStringList(launchArguments.get("vmArgs"));
+                ExplicitProcessParameters params = ExplicitProcessParameters.empty();
+                if (!(args.isEmpty() && vmArgs.isEmpty())) {
+                    ExplicitProcessParameters.Builder bld = ExplicitProcessParameters.builder();
+                    bld.launcherArgs(vmArgs);
+                    bld.args(args);
+                    bld.replaceArgs(false);
+                    params = bld.build();
+                }
+                OperationContext ctx = OperationContext.find(Lookup.getDefault());
+                ctx.addProgressOperationListener(null, new ProgressOperationListener() {
+                    @Override
+                    public void progressHandleCreated(ProgressOperationEvent e) {
+                        context.setProcessExecutorHandle(e.getProgressHandle());
                     }
                 });
-            } else {
-                launchFuture.complete(null);
-            }
-            ActionProgress progress = new ActionProgress() {
-                @Override
-                protected void started() {
-                }
+                TestProgressHandler testProgressHandler = ctx.getClient().getNbCodeCapabilities().hasTestResultsSupport() ? new TestProgressHandler(ctx.getClient(), context.getClient(), Utils.toUri(toRun)) : null;
+                Lookup launchCtx = new ProxyLookup(
+                        testProgressHandler != null ? Lookups.fixed(toRun, ioContext, progress, testProgressHandler) : Lookups.fixed(toRun, ioContext, progress),
+                        Lookup.getDefault()
+                );
 
-                @Override
-                public void finished(boolean success) {
-                    ioContext.stop();
-                    notifyFinished(context, success);
-                }
-            };
-            List<String> args = argsToStringList(launchArguments.get("args"));
-            List<String> vmArgs = argsToStringList(launchArguments.get("vmArgs"));
-            ExplicitProcessParameters params = ExplicitProcessParameters.empty();
-            if (!(args.isEmpty() && vmArgs.isEmpty())) {
-                ExplicitProcessParameters.Builder bld = ExplicitProcessParameters.builder();
-                bld.launcherArgs(vmArgs);
-                bld.args(args);
-                bld.replaceArgs(false);
-                params = bld.build();
-            }
-            OperationContext ctx = OperationContext.find(Lookup.getDefault());
-            ctx.addProgressOperationListener(null, new ProgressOperationListener() {
-                @Override
-                public void progressHandleCreated(ProgressOperationEvent e) {
-                    context.setProcessExecutorHandle(e.getProgressHandle());
+                Lookup lookup;
+                if (singleMethod != null) {
+                    lookup = Lookups.fixed(toRun, singleMethod, params, ioContext, progress);
+                } else {
+                    lookup = Lookups.fixed(toRun, ioContext, params, progress);
                 }
+                Lookups.executeWith(launchCtx, () -> {
+                    providerAndCommand.first().invokeAction(providerAndCommand.second(), lookup);
+
+                });
+            }).exceptionally((t) -> {
+                launchFuture.completeExceptionally(t);
+                return null;
             });
-            TestProgressHandler testProgressHandler = ctx.getClient().getNbCodeCapabilities().hasTestResultsSupport() ? new TestProgressHandler(ctx.getClient(), context.getClient(), Utils.toUri(toRun)) : null;
+        } else {
+            ExecutionDescriptor executionDescriptor = new ExecutionDescriptor()
+                    .showProgress(true)
+                    .showSuspended(true)
+                    .frontWindowOnError(true)
+                    .controllable(true);
             Lookup launchCtx = new ProxyLookup(
-                    testProgressHandler != null ? Lookups.fixed(toRun, ioContext, progress, testProgressHandler) : Lookups.fixed(toRun, ioContext, progress),
+                    Lookups.fixed(ioContext, progress),
                     Lookup.getDefault()
             );
+            List<String> args = argsToStringList(launchArguments.get("args"));
+            Lookups.executeWith(launchCtx, () -> {
+                if (debug) {
+                    String miDebugger = (String) launchArguments.get("miDebugger");
+                    startNativeDebug(nativeImageFile, args, miDebugger, context, executionDescriptor, launchFuture);
+                } else {
+                    execNative(nativeImageFile, args, context, executionDescriptor, launchFuture);//, success);
+                }
+            });
+        }
+        return launchFuture;
+    }
 
-            Lookup lookup;
-            if (singleMethod != null) {
-                lookup = Lookups.fixed(toRun, singleMethod, params, ioContext, progress);
-            } else {
-                lookup = Lookups.fixed(toRun, ioContext, params, progress);
+    private static void execNative(File nativeImageFile, List<String> args, DebugAdapterContext context, ExecutionDescriptor executionDescriptor, CompletableFuture<Void> launchFuture) {
+        ExecutionService.newService(() -> {
+            launchFuture.complete(null);
+            List<String> command = args.isEmpty() ? Collections.singletonList(nativeImageFile.getAbsolutePath()) : join(nativeImageFile.getAbsolutePath(), args);
+            try {
+                return new ProcessBuilder(command).start();
+            } catch (IOException ex) {
+                ErrorUtilities.completeExceptionally(launchFuture,
+                    "Failed to run debuggee native image: " + ex.getLocalizedMessage(),
+                    ResponseErrorCode.serverErrorStart);
+                throw ex;
             }
-            Lookups.executeWith(launchCtx, () -> {
-                providerAndCommand.first().invokeAction(providerAndCommand.second(), lookup);
+        }, executionDescriptor, "Run - " + nativeImageFile.getName()).run();
+    }
+
+    private static List<String> join(String first, List<String> next) {
+        List<String> joined = new ArrayList<>(next.size() + 1);
+        joined.add(first);
+        joined.addAll(next);
+        return joined;
+    }
 
+    private static void startNativeDebug(File nativeImageFile, List<String> args, String miDebugger, DebugAdapterContext context, ExecutionDescriptor executionDescriptor, CompletableFuture<Void> launchFuture) {
+        AtomicReference<NIDebugger> niDebuggerRef = new AtomicReference<>();
+        AtomicReference<NbDebugSession> debugSessionRef = new AtomicReference<>();
+        NIDebugger niDebugger;
+        try {
+            niDebugger = NIDebugRunner.start(nativeImageFile, args, miDebugger, null, null, executionDescriptor, engine -> {
+                Session session = engine.lookupFirst(null, Session.class);
+                NbDebugSession debugSession = new NbDebugSession(session);
+                debugSessionRef.set(debugSession);
+                NIDebugger niDebugger_ = niDebuggerRef.get();
+                if (niDebugger_ != null) {
+                    debugSession.setNIDebugger(niDebugger_);
+                }
+                context.setDebugSession(debugSession);
+                launchFuture.complete(null);
+                context.getConfigurationSemaphore().waitForConfigurationDone();
             });
-        }).exceptionally((t) -> {
-            launchFuture.completeExceptionally(t);
-            return null;
-        });
-        return launchFuture;
+        } catch (IllegalStateException ex) {
+            ErrorUtilities.completeExceptionally(launchFuture,
+                "Failed to launch debuggee native image. " + ex.getLocalizedMessage(),
+                ResponseErrorCode.serverErrorStart);
+            return ;
+        }
+        niDebuggerRef.set(niDebugger);
+        NbDebugSession debugSession = debugSessionRef.get();
+        if (debugSession != null) {
+            debugSession.setNIDebugger(niDebugger);
+        }
     }
-    
+
     @NonNull
     private List<String> argsToStringList(Object o) {
         if (o == null) {
@@ -186,7 +267,7 @@ public abstract class NbLaunchDelegate {
         }
     }
 
-    private CompletableFuture<Pair<ActionProvider, String>> findTargetWithPossibleRebuild(FileObject toRun, SingleMethod singleMethod, boolean debug, boolean testRun, NbProcessConsole ioContext) throws IllegalArgumentException {
+    private static CompletableFuture<Pair<ActionProvider, String>> findTargetWithPossibleRebuild(FileObject toRun, SingleMethod singleMethod, boolean debug, boolean testRun, NbProcessConsole ioContext) throws IllegalArgumentException {
         Pair<ActionProvider, String> providerAndCommand = findTarget(toRun, singleMethod, debug, testRun);
         if (providerAndCommand != null) {
             return CompletableFuture.completedFuture(providerAndCommand);
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
index 51072b5..b20629b 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
@@ -53,6 +53,7 @@ public final class NbLaunchRequestHandler {
     private NbLaunchDelegate activeLaunchHandler;
 
     public CompletableFuture<Void> launch(Map<String, Object> launchArguments, DebugAdapterContext context) {
+        boolean isNative = "nativeimage".equals(launchArguments.get("type"));
         CompletableFuture<Void> resultFuture = new CompletableFuture<>();
         boolean noDebug = (Boolean) launchArguments.getOrDefault("noDebug", Boolean.FALSE);
         Consumer<DebugAdapterContext> terminateHandle = (daContext) -> handleTerminatedEvent(daContext);
@@ -61,8 +62,8 @@ public final class NbLaunchRequestHandler {
         // validation
         List<String> modulePaths = (List<String>) launchArguments.getOrDefault("modulePaths", Collections.emptyList());
         List<String> classPaths = (List<String>) launchArguments.getOrDefault("classPaths", Collections.emptyList());
-        if (StringUtils.isBlank((String)launchArguments.get("mainClass"))
-                || modulePaths.isEmpty() && classPaths.isEmpty()) {
+        if (!isNative && (StringUtils.isBlank((String)launchArguments.get("mainClass"))
+                          || modulePaths.isEmpty() && classPaths.isEmpty())) {
             ErrorUtilities.completeExceptionally(resultFuture,
                 "Failed to launch debuggee VM. Missing mainClass or modulePaths/classPaths options in launch configuration.",
                 ResponseErrorCode.serverErrorStart);
@@ -80,37 +81,52 @@ public final class NbLaunchRequestHandler {
             context.setDebuggeeEncoding(Charset.forName((String)launchArguments.get("encoding")));
         }
 
-        if (StringUtils.isBlank((String)launchArguments.get("vmArgs"))) {
-            launchArguments.put("vmArgs", String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name()));
-        } else {
-            // if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins
-            launchArguments.put("vmArgs", String.format("%s -Dfile.encoding=%s", launchArguments.get("vmArgs"), context.getDebuggeeEncoding().name()));
+        if (!isNative) {
+            if (StringUtils.isBlank((String)launchArguments.get("vmArgs"))) {
+                launchArguments.put("vmArgs", String.format("-Dfile.encoding=%s", context.getDebuggeeEncoding().name()));
+            } else {
+                // if vmArgs already has the file.encoding settings, duplicate options for jvm will not cause an error, the right most value wins
+                launchArguments.put("vmArgs", String.format("%s -Dfile.encoding=%s", launchArguments.get("vmArgs"), context.getDebuggeeEncoding().name()));
+            }
         }
         context.setDebugMode(!noDebug);
 
         activeLaunchHandler.preLaunch(launchArguments, context);
 
         String filePath = (String)launchArguments.get("mainClass");
-        File ioFile = null;
-        if (filePath != null) {
-            ioFile = new File(filePath);
-            if (!ioFile.exists()) {
-                try {
-                    URI uri = new URI(filePath);
-                    ioFile = Utilities.toFile(uri);
-                } catch (URISyntaxException ex) {
-                    // Not a valid file
+        FileObject file = null;
+        File nativeImageFile = null;
+        if (!isNative) {
+            File ioFile = null;
+            if (filePath != null) {
+                ioFile = new File(filePath);
+                if (!ioFile.exists()) {
+                    try {
+                        URI uri = new URI(filePath);
+                        ioFile = Utilities.toFile(uri);
+                    } catch (URISyntaxException ex) {
+                        // Not a valid file
+                    }
                 }
             }
-        }
-        FileObject file = ioFile != null ? FileUtil.toFileObject(ioFile) : null;
-        if (file == null) {
-            ErrorUtilities.completeExceptionally(resultFuture,
-                    "Missing file: " + filePath,
+            file = ioFile != null ? FileUtil.toFileObject(ioFile) : null;
+            if (file == null) {
+                ErrorUtilities.completeExceptionally(resultFuture,
+                        "Missing file: " + filePath,
+                        ResponseErrorCode.serverErrorStart);
+                return resultFuture;
+            }
+        } else {
+            String nativeImage = (String) launchArguments.get("nativeImagePath");
+            if (nativeImage == null) {
+                ErrorUtilities.completeExceptionally(resultFuture,
+                    "Failed to launch debuggee native image. No native image is specified.",
                     ResponseErrorCode.serverErrorStart);
-            return resultFuture;
+                return resultFuture;
+            }
+            nativeImageFile = new File(nativeImage);
         }
-        if (!launchArguments.containsKey("sourcePaths")) {
+        if (!isNative && !launchArguments.containsKey("sourcePaths")) {
             ClassPath sourceCP = ClassPath.getClassPath(file, ClassPath.SOURCE);
             if (sourceCP != null) {
                 FileObject[] roots = sourceCP.getRoots();
@@ -125,7 +141,7 @@ public final class NbLaunchRequestHandler {
         }
         String singleMethod = (String)launchArguments.get("methodName");
         boolean testRun = (Boolean) launchArguments.getOrDefault("testRun", Boolean.FALSE);
-        activeLaunchHandler.nbLaunch(file, singleMethod, launchArguments, context, !noDebug, testRun, new OutputListener(context)).thenRun(() -> {
+        activeLaunchHandler.nbLaunch(file, nativeImageFile, singleMethod, launchArguments, context, !noDebug, testRun, new OutputListener(context)).thenRun(() -> {
             activeLaunchHandler.postLaunch(launchArguments, context);
             resultFuture.complete(null);
         }).exceptionally(e -> {
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/variables/NbVariablesRequestHandler.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/variables/NbVariablesRequestHandler.java
index bd76604..b1627d2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/variables/NbVariablesRequestHandler.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/variables/NbVariablesRequestHandler.java
@@ -28,8 +28,8 @@ import org.eclipse.lsp4j.debug.Variable;
 import org.eclipse.lsp4j.debug.VariablesArguments;
 import org.eclipse.lsp4j.debug.VariablesResponse;
 import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
+import org.netbeans.api.debugger.Session;
 
-import org.netbeans.api.debugger.jpda.JPDADebugger;
 import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
 import org.netbeans.modules.java.lsp.server.debugging.NbScope;
 import org.netbeans.modules.java.lsp.server.debugging.launch.NbDebugSession;
@@ -63,8 +63,8 @@ public final class NbVariablesRequestHandler {
             // Nothing, or an old container
             response.setVariables(new Variable[0]);
         } else {
-            JPDADebugger debugger = context.getDebugSession().getDebugger();
-            Models.CompoundModel localsModel = localsModelProvider.getModel(debugger.getSession());
+            Session session = context.getDebugSession().getSession();
+            Models.CompoundModel localsModel = localsModelProvider.getModel(session);
             int threadId;
             if (container instanceof NbScope) {
                 threadId = ((NbScope) container).getFrame().getThreadId();
@@ -84,14 +84,21 @@ public final class NbVariablesRequestHandler {
                 }
                 for (Object child : children) {
                     String name = localsModel.getDisplayName(child);
-                    String value = String.valueOf(localsModel.getValueAt(child, LOCALS_TO_STRING_COLUMN_ID));
+                    String value;
+                    try {
+                        value = String.valueOf(localsModel.getValueAt(child, LOCALS_TO_STRING_COLUMN_ID));
+                    } catch (UnknownTypeException ex) {
+                        value = String.valueOf(localsModel.getValueAt(child, LOCALS_VALUE_COLUMN_ID));
+                    }
                     String type = String.valueOf(localsModel.getValueAt(child, LOCALS_TYPE_COLUMN_ID));
-                    int id = context.getThreadsProvider().getThreadObjects().addObject(threadId, child);
                     Variable variable = new Variable();
                     variable.setName(name);
                     variable.setValue(value);
                     variable.setType(type);
-                    variable.setVariablesReference(id);
+                    if (!localsModel.isLeaf(child)) {
+                        int id = context.getThreadsProvider().getThreadObjects().addObject(threadId, child);
+                        variable.setVariablesReference(id);
+                    }
                     list.add(variable);
                 }
             } catch (UnknownTypeException e) {
@@ -132,8 +139,8 @@ public final class NbVariablesRequestHandler {
             return future;
         }
 
-        JPDADebugger debugger = ((NbDebugSession) context.getDebugSession()).getDebugger();
-        Models.CompoundModel localsModel = localsModelProvider.getModel(debugger.getSession());
+        Session session = ((NbDebugSession) context.getDebugSession()).getSession();
+        Models.CompoundModel localsModel = localsModelProvider.getModel(session);
 
         int threadId;
         if (container instanceof NbScope) {
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index 45b3736..f63cec2 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -148,6 +148,61 @@
 						}
 					}
 				]
+			},
+			{
+				"type": "nativeimage",
+				"label": "Native Image",
+				"runtime": "node",
+				"languages": [
+					"java"
+				],
+				"configurationAttributes": {
+					"launch": {
+						"required": [
+							"nativeImagePath"
+						],
+						"properties": {
+							"nativeImagePath": {
+								"type": "string",
+								"description": "Absolute path to the application native image.",
+								"default": "${workspaceFolder}/build/native-image/application"
+							},
+							"miDebugger": {
+								"type": "string",
+								"description": "MI Debugger",
+								"default": "gdb"
+							},
+							"console": {
+								"type": "string",
+								"enum": [
+									"internalConsole"
+								],
+								"description": "The specified console to launch the program.",
+								"default": "internalConsole"
+							}
+						}
+					}
+				},
+				"initialConfigurations": [
+					{
+						"type": "nativeimage",
+						"request": "launch",
+						"name": "Launch Native Image",
+						"nativeImagePath": "${workspaceFolder}/build/native-image/application"
+					}
+				],
+				"configurationSnippets": [
+					{
+						"label": "Launch Native Image",
+						"description": "Launch a native image with MI debugger.",
+						"body": {
+							"type": "nativeimage",
+							"request": "launch",
+							"name": "Launch Native Image",
+							"nativeImagePath": "^\"${1:\\${workspaceFolder\\}/build/native-image/application}\""
+						}
+					}
+				]
 			}
 		],
 		"commands": [
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index bc83afe..e0a6bfb 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -174,9 +174,12 @@ export function activate(context: ExtensionContext): VSNetBeansAPI {
     //register debugger:
     let configProvider = new NetBeansConfigurationProvider();
     context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('java8+', configProvider));
+    let configNativeProvider = new NetBeansConfigurationNativeProvider();
+    context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('nativeimage', configNativeProvider));
 
     let debugDescriptionFactory = new NetBeansDebugAdapterDescriptionFactory();
     context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('java8+', debugDescriptionFactory));
+    context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('nativeimage', debugDescriptionFactory));
 
     // register commands
     context.subscriptions.push(commands.registerCommand('java.workspace.compile', () => {
@@ -655,3 +658,26 @@ class NetBeansConfigurationProvider implements vscode.DebugConfigurationProvider
         return config;
     }
 }
+
+class NetBeansConfigurationNativeProvider implements vscode.DebugConfigurationProvider {
+
+    resolveDebugConfiguration(_folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, _token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
+        if (!config.type) {
+            config.type = 'nativeimage';
+        }
+        if (!config.request) {
+            config.request = 'launch';
+        }
+        if (!config.nativeImagePath) {
+            config.nativeImagePath = '${workspaceFolder}/build/native-image/application';
+        }
+        if (!config.miDebugger) {
+            config.miDebugger = 'gdb';
+        }
+        if (!config.console) {
+            config.console = 'internalConsole';
+        }
+
+        return config;
+    }
+}
diff --git a/java/java.nativeimage.debugger/build.xml b/java/java.nativeimage.debugger/build.xml
new file mode 100644
index 0000000..6f55714
--- /dev/null
+++ b/java/java.nativeimage.debugger/build.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
+<!-- for some information on what you could do (e.g. targets to override). -->
+<!-- If you delete this file and reopen the project it will be recreated. -->
+<project name="java/java.nativeimage.debugger" default="netbeans" basedir=".">
+    <description>Builds, tests, and runs the project org.netbeans.modules.java.nativeimage.debugger.</description>
+    <import file="../../nbbuild/templates/projectized.xml"/>
+</project>
diff --git a/java/java.nativeimage.debugger/manifest.mf b/java/java.nativeimage.debugger/manifest.mf
new file mode 100644
index 0000000..7fd02de
--- /dev/null
+++ b/java/java.nativeimage.debugger/manifest.mf
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+AutoUpdate-Show-In-Client: false
+OpenIDE-Module: org.netbeans.modules.java.nativeimage.debugger/0
+OpenIDE-Module-Layer: org/netbeans/modules/java/nativeimage/debugger/resources/mf-layer.xml
+OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/nativeimage/debugger/Bundle.properties
+OpenIDE-Module-Specification-Version: 0.1
+
diff --git a/java/java.nativeimage.debugger/nbproject/project.properties b/java/java.nativeimage.debugger/nbproject/project.properties
new file mode 100644
index 0000000..5137752
--- /dev/null
+++ b/java/java.nativeimage.debugger/nbproject/project.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+javac.source=1.8
+javac.compilerargs=-Xlint -Xlint:-serial
diff --git a/cpplite/cpplite.debugger/nbproject/project.xml b/java/java.nativeimage.debugger/nbproject/project.xml
similarity index 78%
copy from cpplite/cpplite.debugger/nbproject/project.xml
copy to java/java.nativeimage.debugger/nbproject/project.xml
index d360c06..21f442c 100644
--- a/cpplite/cpplite.debugger/nbproject/project.xml
+++ b/java/java.nativeimage.debugger/nbproject/project.xml
@@ -23,7 +23,7 @@
     <type>org.netbeans.modules.apisupport.project</type>
     <configuration>
         <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
-            <code-name-base>org.netbeans.modules.cpplite.debugger</code-name-base>
+            <code-name-base>org.netbeans.modules.java.nativeimage.debugger</code-name-base>
             <module-dependencies>
                 <dependency>
                     <code-name-base>org.netbeans.api.annotations.common</code-name-base>
@@ -44,74 +44,96 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.modules.dlight.nativeexecution</code-name-base>
+                    <code-name-base>org.netbeans.api.debugger.jpda</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>1.48</specification-version>
+                        <release-version>2</release-version>
+                        <specification-version>3.20</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.modules.projectapi</code-name-base>
+                    <code-name-base>org.netbeans.api.java.classpath</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>1.75</specification-version>
+                        <specification-version>1.65</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.spi.debugger.ui</code-name-base>
+                    <code-name-base>org.netbeans.modules.extexecution</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>2</release-version>
+                        <specification-version>1.59</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.netbeans.modules.java.project</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
                         <release-version>1</release-version>
-                        <specification-version>2.62</specification-version>
+                        <specification-version>1.83</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.netbeans.spi.viewmodel</code-name-base>
+                    <code-name-base>org.netbeans.modules.nativeimage.api</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <release-version>2</release-version>
-                        <specification-version>1.59</specification-version>
+                        <release-version>0</release-version>
+                        <specification-version>0.1</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.awt</code-name-base>
+                    <code-name-base>org.netbeans.modules.projectapi</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>7.76</specification-version>
+                        <release-version>1</release-version>
+                        <specification-version>1.75</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.filesystems</code-name-base>
+                    <code-name-base>org.netbeans.modules.projectuiapi</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.18</specification-version>
+                        <release-version>1</release-version>
+                        <specification-version>1.99</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.loaders</code-name-base>
+                    <code-name-base>org.netbeans.spi.debugger.ui</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>7.76</specification-version>
+                        <release-version>1</release-version>
+                        <specification-version>2.62</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.modules</code-name-base>
+                    <code-name-base>org.netbeans.spi.viewmodel</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>7.56</specification-version>
+                        <release-version>2</release-version>
+                        <specification-version>1.59</specification-version>
+                    </run-dependency>
+                </dependency>
+                <dependency>
+                    <code-name-base>org.openide.awt</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>7.76</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.nodes</code-name-base>
+                    <code-name-base>org.openide.dialogs</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
@@ -119,43 +141,43 @@
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.text</code-name-base>
+                    <code-name-base>org.openide.filesystems</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>6.75</specification-version>
+                        <specification-version>9.18</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util</code-name-base>
+                    <code-name-base>org.openide.modules</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.15</specification-version>
+                        <specification-version>7.60</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util.lookup</code-name-base>
+                    <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>8.41</specification-version>
+                        <specification-version>9.15</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.util.ui</code-name-base>
+                    <code-name-base>org.openide.util.lookup</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.16</specification-version>
+                        <specification-version>8.41</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
-                    <code-name-base>org.openide.windows</code-name-base>
+                    <code-name-base>org.openide.util.ui</code-name-base>
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>6.85</specification-version>
+                        <specification-version>9.20</specification-version>
                     </run-dependency>
                 </dependency>
             </module-dependencies>
@@ -186,13 +208,9 @@
                 </test-type>
             </test-dependencies>
             <friend-packages>
-                <friend>org.netbeans.modules.cpplite.project</friend>
-                <package>org.netbeans.modules.cpplite.debugger.api</package>
+                <friend>org.netbeans.modules.java.lsp.server</friend>
+                <package>org.netbeans.modules.java.nativeimage.debugger.api</package>
             </friend-packages>
-            <class-path-extension>
-                <runtime-relative-path>ext/cpplite-mi-f8f8250283be.jar</runtime-relative-path>
-                <binary-origin>external/cpplite-mi-f8f8250283be.jar</binary-origin>
-            </class-path-extension>
         </data>
     </configuration>
 </project>
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/Bundle.properties b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/Bundle.properties
new file mode 100644
index 0000000..9afa08a
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/Bundle.properties
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# manifest's description entries
+OpenIDE-Module-Display-Category=Debugging
+OpenIDE-Module-Name=JVM Native Image Debugger
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties
new file mode 100644
index 0000000..d033bba
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+NIAttachCustomizer.fileLabel.text=Image File:
+NIAttachCustomizer.fileTextField.text=
+NIAttachCustomizer.fileButton.text=Browse
+NIAttachCustomizer.dbgLabel.text=Debugger:
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.form b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.form
new file mode 100644
index 0000000..541b070
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.form
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+
+<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+  <AuxValues>
+    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
+    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+  </AuxValues>
+
+  <Layout>
+    <DimensionLayout dim="0">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Group type="102" alignment="0" attributes="0">
+                      <Component id="fileLabel" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="fileTextField" max="32767" attributes="0"/>
+                  </Group>
+                  <Group type="102" alignment="0" attributes="0">
+                      <Component id="dbgLabel" min="-2" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="dbgComboBox" pref="196" max="32767" attributes="0"/>
+                  </Group>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Component id="fileButton" min="-2" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+    <DimensionLayout dim="1">
+      <Group type="103" groupAlignment="0" attributes="0">
+          <Group type="102" alignment="0" attributes="0">
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="fileLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="fileTextField" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="fileButton" alignment="3" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="dbgLabel" alignment="3" min="-2" max="-2" attributes="0"/>
+                  <Component id="dbgComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
+              </Group>
+              <EmptySpace max="32767" attributes="0"/>
+          </Group>
+      </Group>
+    </DimensionLayout>
+  </Layout>
+  <SubComponents>
+    <Component class="javax.swing.JLabel" name="fileLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties" key="NIAttachCustomizer.fileLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JTextField" name="fileTextField">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties" key="NIAttachCustomizer.fileTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JButton" name="fileButton">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties" key="NIAttachCustomizer.fileButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileButtonActionPerformed"/>
+      </Events>
+    </Component>
+    <Component class="javax.swing.JLabel" name="dbgLabel">
+      <Properties>
+        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString bundle="org/netbeans/modules/java/nativeimage/debugger/actions/Bundle.properties" key="NIAttachCustomizer.dbgLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JComboBox" name="dbgComboBox">
+      <Properties>
+        <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
+          <StringArray count="2">
+            <StringItem index="0" value="gdb"/>
+            <StringItem index="1" value="lldb-mi"/>
+          </StringArray>
+        </Property>
+      </Properties>
+      <AuxValues>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+      </AuxValues>
+    </Component>
+  </SubComponents>
+</Form>
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java
new file mode 100644
index 0000000..0d1fe63
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.actions;
+
+import java.awt.event.ActionEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import javax.swing.Action;
+import javax.swing.JFileChooser;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.filechooser.FileFilter;
+
+import org.netbeans.api.debugger.Properties;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.java.nativeimage.debugger.api.NIDebugRunner;
+import org.netbeans.spi.debugger.ui.Controller;
+import static org.netbeans.spi.debugger.ui.Controller.PROP_VALID;
+import org.netbeans.spi.debugger.ui.PersistentController;
+import static org.netbeans.spi.project.ActionProvider.COMMAND_DEBUG;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.awt.Actions;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.ModuleInfo;
+import org.openide.modules.Modules;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+import org.openide.util.RequestProcessor;
+import org.openide.util.Utilities;
+
+/**
+ *
+ * @author martin
+ */
+public class NIAttachCustomizer extends javax.swing.JPanel {
+
+    private final ConnectController controller;
+    private final ValidityDocumentListener validityDocumentListener = new ValidityDocumentListener();
+    private final RequestProcessor currentFileRP = new RequestProcessor(NIAttachCustomizer.class);
+
+    /**
+     * Creates new form NIAttachCustomizer
+     */
+    public NIAttachCustomizer() {
+        controller = new ConnectController();
+        initComponents();
+        fileTextField.getDocument().addDocumentListener(validityDocumentListener);
+        initNIFile();
+    }
+
+    private void initNIFile() {
+        currentFileRP.post(() -> {
+            FileObject currentFO = Utilities.actionsGlobalContext().lookup(FileObject.class);
+            if (currentFO != null) {
+                File currentFile = FileUtil.toFile(currentFO);
+                String path;
+                if (currentFile != null && currentFile.canExecute()) {
+                    path = currentFile.getAbsolutePath();
+                } else {
+                    Project project = FileOwnerQuery.getOwner(currentFO);
+                    if (project != null) {
+                        currentFO = project.getProjectDirectory();
+                        currentFile = FileUtil.toFile(currentFO);
+                        path = currentFile.getAbsolutePath();
+                    } else {
+                        path = null;
+                    }
+                }
+                if (path != null) {
+                    SwingUtilities.invokeLater(() -> {
+                        fileTextField.setText(path);
+                    });
+                }
+            }
+        });
+    }
+
+    /**
+     * This method is called from within the constructor to initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is always
+     * regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        fileLabel = new javax.swing.JLabel();
+        fileTextField = new javax.swing.JTextField();
+        fileButton = new javax.swing.JButton();
+        dbgLabel = new javax.swing.JLabel();
+        dbgComboBox = new javax.swing.JComboBox<>();
+
+        org.openide.awt.Mnemonics.setLocalizedText(fileLabel, org.openide.util.NbBundle.getMessage(NIAttachCustomizer.class, "NIAttachCustomizer.fileLabel.text")); // NOI18N
+
+        fileTextField.setText(org.openide.util.NbBundle.getMessage(NIAttachCustomizer.class, "NIAttachCustomizer.fileTextField.text")); // NOI18N
+
+        org.openide.awt.Mnemonics.setLocalizedText(fileButton, org.openide.util.NbBundle.getMessage(NIAttachCustomizer.class, "NIAttachCustomizer.fileButton.text")); // NOI18N
+        fileButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                fileButtonActionPerformed(evt);
+            }
+        });
+
+        org.openide.awt.Mnemonics.setLocalizedText(dbgLabel, org.openide.util.NbBundle.getMessage(NIAttachCustomizer.class, "NIAttachCustomizer.dbgLabel.text")); // NOI18N
+
+        dbgComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "gdb", "lldb-mi" }));
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+        this.setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(fileLabel)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(fileTextField))
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(dbgLabel)
+                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                        .addComponent(dbgComboBox, 0, 196, Short.MAX_VALUE)))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(fileButton)
+                .addContainerGap())
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(layout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(fileLabel)
+                    .addComponent(fileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+                    .addComponent(fileButton))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(dbgLabel)
+                    .addComponent(dbgComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+        );
+    }// </editor-fold>//GEN-END:initComponents
+
+    @NbBundle.Messages({"CTL_ExecutableFiles=Executable Files"})
+    private void fileButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileButtonActionPerformed
+        JFileChooser chooser = new JFileChooser(fileTextField.getText());
+        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        chooser.setFileFilter(new FileFilter() {
+            @Override
+            public boolean accept(File f) {
+                return f.isDirectory() || f.canExecute();
+            }
+
+            @Override
+            public String getDescription() {
+                return Bundle.CTL_ExecutableFiles();
+            }
+        });
+        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+            fileTextField.setText(chooser.getSelectedFile().getAbsolutePath());
+        }
+    }//GEN-LAST:event_fileButtonActionPerformed
+
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JComboBox<String> dbgComboBox;
+    private javax.swing.JLabel dbgLabel;
+    private javax.swing.JButton fileButton;
+    private javax.swing.JLabel fileLabel;
+    private javax.swing.JTextField fileTextField;
+    // End of variables declaration//GEN-END:variables
+
+    RequestProcessor.Task validationTask = currentFileRP.create(new FileValidationTask());
+
+    @NbBundle.Messages({"MSG_NoFile=Native Imige File is missing."})
+    private void checkValid() {
+        assert SwingUtilities.isEventDispatchThread() : "Called outside of AWT.";
+        if (fileTextField.getText().isEmpty()) {
+            controller.setInformationMessage(Bundle.MSG_NoFile());
+            controller.setValid(false);
+            return ;
+        }
+        validationTask.schedule(200);
+    }
+
+    private class FileValidationTask implements Runnable {
+
+        @Override
+        public void run() {
+            String filePath = fileTextField.getText();
+            File file = new File(filePath);
+            boolean canExecute = file.isFile() && file.canExecute();
+            SwingUtilities.invokeLater(() -> {
+                controller.setValid(canExecute);
+                String message = canExecute ? null : Bundle.MSG_NoFile();
+                controller.setInformationMessage(message);
+            });
+        }
+    }
+
+    private class ValidityDocumentListener implements DocumentListener {
+        @Override
+        public void insertUpdate(DocumentEvent e) {
+            checkValid();
+        }
+        @Override
+        public void removeUpdate(DocumentEvent e) {
+            checkValid();
+        }
+        @Override
+        public void changedUpdate(DocumentEvent e) {
+            checkValid();
+        }
+    }
+
+    Controller getController() {
+        return controller;
+    }
+
+    private static final String CPPLITE_DEBUGGER = "org.netbeans.modules.cpplite.debugger"; // NOI18N
+
+    @NbBundle.Messages({"MSG_EnableNativeDebugger=Enable {0} in Plugins Manager", "TTL_EnableNativeDebugger=Native Debugger Dependency"})
+    private static boolean checkCPPLite() {
+        ModuleInfo cppliteDebugger = Modules.getDefault().findCodeNameBase(CPPLITE_DEBUGGER);
+        if (cppliteDebugger != null && !cppliteDebugger.isEnabled()) {
+            Action pluginsManager = Actions.forID("System", "org.netbeans.modules.autoupdate.ui.actions.PluginManagerAction");
+            String moduleDisplayName = cppliteDebugger.getDisplayName();
+            NotifyDescriptor messageDescriptor = new NotifyDescriptor.Confirmation(
+                    Bundle.MSG_EnableNativeDebugger(moduleDisplayName),
+                    Bundle.TTL_EnableNativeDebugger(),
+                    NotifyDescriptor.OK_CANCEL_OPTION);
+            if (NotifyDescriptor.OK_OPTION.equals(DialogDisplayer.getDefault().notify(messageDescriptor))) {
+                SwingUtilities.invokeLater(() -> {
+                    ActionEvent ev = new ActionEvent(pluginsManager, 100, "installed");
+                    pluginsManager.actionPerformed(ev);
+                });
+            }
+            return false;
+        }
+        return true;
+    }
+
+    public class ConnectController implements PersistentController {
+
+        private static final String NI_ATTACH_PROPERTIES = "native_image_attach_settings";
+        private static final String PROP_NI_FILE = "niFile";
+        private static final String PROP_DBG = "debugger";
+
+        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+        private boolean valid = true;
+
+        @Override
+        public String getDisplayName() {
+            return dbgComboBox.getSelectedItem() + " " + new File(fileTextField.getText()).getName();
+        }
+
+        @Override
+        public boolean load(Properties props) {
+            assert !SwingUtilities.isEventDispatchThread();
+            final Properties attachProps = props.getProperties(NI_ATTACH_PROPERTIES);
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    @Override
+                    public void run() {
+                        fileTextField.setText(attachProps.getString(PROP_NI_FILE, ""));
+                        dbgComboBox.setSelectedItem(attachProps.getString(PROP_DBG, "DBG"));
+                    }
+                });
+            } catch (InterruptedException | InvocationTargetException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+            return true;
+        }
+
+        @Override
+        public void save(Properties props) {
+            final Properties attachProps = props.getProperties(NI_ATTACH_PROPERTIES);
+            if (SwingUtilities.isEventDispatchThread()) {
+                saveToProps(attachProps);
+            } else {
+                try {
+                    SwingUtilities.invokeAndWait(new Runnable() {
+                        @Override
+                        public void run() {
+                            saveToProps(attachProps);
+
+                        }
+                    });
+                } catch (InterruptedException ex) {
+                    Exceptions.printStackTrace(ex);
+                } catch (InvocationTargetException ex) {
+                    Exceptions.printStackTrace(ex);
+                }
+            }
+        }
+
+        private void saveToProps(Properties attachProps) {
+            attachProps.setString(PROP_NI_FILE, fileTextField.getText());
+            attachProps.setString(PROP_DBG, (String) dbgComboBox.getSelectedItem());
+        }
+
+        @Override
+        public boolean ok() {
+            String filePath = fileTextField.getText();
+            String debuggerCommand = dbgComboBox.getSelectedItem().toString();
+            currentFileRP.post(() -> {
+                if (!checkCPPLite()) {
+                    return ;
+                }
+                File file = new File(filePath);
+                String displayName = COMMAND_DEBUG + " " + file.getName();
+                NIDebugRunner.start(file, Collections.emptyList(), debuggerCommand, null, displayName, null, null);
+            });
+            return true;
+        }
+
+        @Override
+        public boolean cancel() {
+            return true;
+        }
+
+        @Override
+        public boolean isValid() {
+            return valid;
+        }
+
+        void setValid(boolean valid) {
+            this.valid = valid;
+            firePropertyChange(PROP_VALID, !valid, valid);
+        }
+
+        void setErrorMessage(String msg) {
+            firePropertyChange(NotifyDescriptor.PROP_ERROR_NOTIFICATION, null, msg);
+        }
+
+        void setInformationMessage(String msg) {
+            firePropertyChange(NotifyDescriptor.PROP_INFO_NOTIFICATION, null, msg);
+        }
+
+        private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
+            pcs.firePropertyChange(propertyName, oldValue, newValue);
+        }
+
+        @Override
+        public void addPropertyChangeListener(PropertyChangeListener l) {
+            pcs.addPropertyChangeListener(l);
+        }
+
+        @Override
+        public void removePropertyChangeListener(PropertyChangeListener l) {
+            pcs.removePropertyChangeListener(l);
+        }
+
+    }
+
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachType.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachType.java
new file mode 100644
index 0000000..ba21ff0
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachType.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.actions;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import javax.swing.JComponent;
+
+import org.netbeans.spi.debugger.ui.AttachType;
+import org.netbeans.spi.debugger.ui.Controller;
+import org.openide.util.NbBundle;
+
+/**
+ *
+ * @author martin
+ */
+@NbBundle.Messages("CTL_NIConnector_name=Native Image")
+@AttachType.Registration(displayName="#CTL_NIConnector_name")
+public class NIAttachType extends AttachType {
+
+    private Reference<NIAttachCustomizer> customizerRef = new WeakReference<>(null);
+
+    @Override
+    public JComponent getCustomizer() {
+        NIAttachCustomizer ac = new NIAttachCustomizer();
+        customizerRef = new WeakReference<>(ac);
+        return ac;
+    }
+
+    @Override
+    public Controller getController() {
+        NIAttachCustomizer panel = customizerRef.get();
+        if (panel != null) {
+            return panel.getController();
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java
new file mode 100644
index 0000000..cbb6770
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.api;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.netbeans.api.debugger.DebuggerEngine;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.java.nativeimage.debugger.breakpoints.JPDABreakpointsHandler;
+import org.netbeans.modules.java.nativeimage.debugger.displayer.JavaFrameDisplayer;
+import org.netbeans.modules.java.nativeimage.debugger.displayer.JavaVariablesDisplayer;
+import static org.netbeans.spi.project.ActionProvider.COMMAND_DEBUG;
+
+/**
+ * Runs debugger with Java translations on a native image.
+ *
+ * @author martin
+ */
+public final class NIDebugRunner {
+
+    private NIDebugRunner() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Starts Native Image debugger.
+     *
+     * @param niFile Native Image file
+     * @param arguments a list of arguments when executing the native image
+     * @param debuggerCommand the debugger command
+     * @param project a project associated with the native image, or <code>null</code>
+     * @param displayName display name of the execution
+     * @param executionDescriptor execution descriptor
+     * @param startedEngine consumer of the started {@link DebuggerEngine}.
+     * @return an instance of {@link NIDebugger}.
+     * @throws IllegalStateException when the native debugger is not available.
+     */
+    public static NIDebugger start(File niFile, List<String> arguments, String debuggerCommand, Project project, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine) throws IllegalStateException {
+        JavaVariablesDisplayer variablesDisplayer = new JavaVariablesDisplayer();
+        JavaFrameDisplayer frameDisplayer = new JavaFrameDisplayer(project);
+        NIDebugger debugger = NIDebugger.newBuilder()
+                .frameDisplayer(frameDisplayer)
+                .variablesDisplayer(variablesDisplayer)
+                .build();
+        variablesDisplayer.setDebugger(debugger);
+        JPDABreakpointsHandler breakpointsHandler = new JPDABreakpointsHandler(niFile, debugger);
+        File workingDirectory = new File(System.getProperty("user.dir"));
+        List<String> command = arguments.isEmpty() ? Collections.singletonList(niFile.getAbsolutePath()) : join(niFile.getAbsolutePath(), arguments);
+        debugger.start(
+                command,
+                workingDirectory,
+                debuggerCommand,
+                COMMAND_DEBUG + " " + niFile.getName(),
+                executionDescriptor,
+                startedEngine).thenRun(() -> {
+                    breakpointsHandler.dispose();
+                });
+        return debugger;
+    }
+
+    private static List<String> join(String first, List<String> next) {
+        List<String> joined = new ArrayList<>(next.size() + 1);
+        joined.add(first);
+        joined.addAll(next);
+        return joined;
+    }
+
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/breakpoints/JPDABreakpointsHandler.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/breakpoints/JPDABreakpointsHandler.java
new file mode 100644
index 0000000..2f43091
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/breakpoints/JPDABreakpointsHandler.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.breakpoints;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.debugger.DebuggerManagerAdapter;
+import org.netbeans.api.debugger.jpda.JPDABreakpoint;
+import org.netbeans.api.debugger.jpda.LineBreakpoint;
+import org.netbeans.api.java.classpath.GlobalPathRegistry;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.filesystems.URLMapper;
+
+/**
+ * Gathers JPDA breakpoints and submits them to the native debugger.
+ */
+public class JPDABreakpointsHandler extends DebuggerManagerAdapter implements PropertyChangeListener {
+
+    private final File niFileSources;
+    private final NIDebugger debugger;
+    private final Set<JPDABreakpoint> attachedBreakpoints = new HashSet<>();
+
+    public JPDABreakpointsHandler(File niFile, NIDebugger debugger) {
+        this.niFileSources = getNativeSources(niFile);
+        this.debugger = debugger;
+        DebuggerManager dm = DebuggerManager.getDebuggerManager();
+        dm.addDebuggerListener(DebuggerManager.PROP_BREAKPOINTS, this);
+        Breakpoint[] bs = dm.getBreakpoints();
+        for (Breakpoint b : bs) {
+            add(b);
+        }
+    }
+
+    private static File getNativeSources(File niFile) {
+        File sources = new File(niFile.getParentFile(), "sources");
+        if (sources.isDirectory()) {
+            return sources;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void breakpointAdded(Breakpoint breakpoint) {
+        add(breakpoint);
+    }
+
+    @Override
+    public void breakpointRemoved(Breakpoint breakpoint) {
+        if (breakpoint instanceof JPDABreakpoint) {
+            breakpoint.removePropertyChangeListener(this);
+            debugger.removeBreakpoint(breakpoint);
+            synchronized (attachedBreakpoints) {
+                attachedBreakpoints.remove(breakpoint);
+            }
+        }
+    }
+
+    @Override
+    public void propertyChange(PropertyChangeEvent evt) {
+        Object source = evt.getSource();
+        String propertyName = evt.getPropertyName();
+        if (source instanceof JPDABreakpoint && !(Breakpoint.PROP_DISPOSED.equals(propertyName) || Breakpoint.PROP_VALIDITY.equals(propertyName))) {
+            // Change of breakpoint  properties
+            added((JPDABreakpoint) source);
+        }
+    }
+
+    private void add(Breakpoint b) {
+        if (b instanceof JPDABreakpoint && !((JPDABreakpoint) b).isHidden()) {
+            JPDABreakpoint jb = (JPDABreakpoint) b;
+            jb.addPropertyChangeListener(this);
+            synchronized (attachedBreakpoints) {
+                attachedBreakpoints.add(jb);
+            }
+            Breakpoint nativeBreakpoint = added(jb);
+            if (nativeBreakpoint != null) {
+                nativeBreakpoint.addPropertyChangeListener(Breakpoint.PROP_VALIDITY, e -> {
+                    Breakpoint.VALIDITY validity = nativeBreakpoint.getValidity();
+                    String validityMessage = nativeBreakpoint.getValidityMessage();
+                    ((ChangeListener) jb).stateChanged(new ValidityChanger(validity, validityMessage));
+                });
+            }
+        }
+    }
+
+    private Breakpoint added(JPDABreakpoint b) {
+        if (b instanceof LineBreakpoint) {
+            LineBreakpoint lb = (LineBreakpoint) b;
+            URL url;
+            try {
+                url = new URL(lb.getURL());
+            } catch (MalformedURLException ex) {
+                return null;
+            }
+            String filePath = null;
+            if (niFileSources != null) {
+                FileObject fo;
+                fo = URLMapper.findFileObject(url);
+                for (FileObject root : GlobalPathRegistry.getDefault().getSourceRoots()) {
+                    if (FileUtil.isParentOf(root, fo)) {
+                        String path = FileUtil.getRelativePath(root, fo);
+                        File sourcesFile = new File(niFileSources, path);
+                        filePath = sourcesFile.getAbsolutePath();
+                        break;
+                    }
+                }
+            }
+            if (filePath == null) {
+                try {
+                    filePath = new File(url.toURI()).getAbsolutePath();
+                } catch (URISyntaxException ex) {
+                    return null;
+                }
+            }
+            NILineBreakpointDescriptor niBreakpointDescriptor = NILineBreakpointDescriptor.newBuilder(filePath, lb.getLineNumber())
+                    .condition(lb.getCondition())
+                    .enabled(lb.isEnabled())
+                    .hidden(true)
+                    .build();
+            Object nativeBreakpoint = debugger.addLineBreakpoint(lb, niBreakpointDescriptor);
+            return (Breakpoint) nativeBreakpoint;
+        }
+        return null;
+    }
+
+    public void dispose() {
+        synchronized (attachedBreakpoints) {
+            for (JPDABreakpoint jb : attachedBreakpoints) {
+                jb.removePropertyChangeListener(this);
+                debugger.removeBreakpoint(jb);
+                ((ChangeListener) jb).stateChanged(new ValidityChanger(Breakpoint.VALIDITY.UNKNOWN, null));
+            }
+            attachedBreakpoints.clear();
+        }
+        DebuggerManager.getDebuggerManager().removeDebuggerListener(DebuggerManager.PROP_BREAKPOINTS, this);
+    }
+
+    private static class ValidityChanger extends ChangeEvent {
+
+        private final String validityMessage;
+
+        ValidityChanger(Breakpoint.VALIDITY validity, String validityMessage) {
+            super(validity);
+            this.validityMessage = validityMessage;
+        }
+
+        @Override
+        public String toString() {
+            return validityMessage;
+        }
+    }
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java
new file mode 100644
index 0000000..2123c98
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaFrameDisplayer.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.displayer;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.netbeans.api.java.classpath.ClassPath;
+import org.netbeans.api.java.classpath.GlobalPathRegistry;
+import org.netbeans.api.java.project.JavaProjectConstants;
+import org.netbeans.api.java.queries.SourceForBinaryQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.netbeans.spi.java.classpath.PathResourceImplementation;
+import org.netbeans.spi.java.classpath.support.ClassPathSupport;
+import org.openide.filesystems.FileObject;
+
+/**
+ * Display native frames like Java frames.
+ *
+ * @author martin
+ */
+public final class JavaFrameDisplayer implements FrameDisplayer {
+
+    private final ClassPath sourcePath;
+
+    public JavaFrameDisplayer(Project project) {
+        this.sourcePath = findSourcePath(project);
+    }
+
+    @Override
+    public DisplayedFrame displayed(NIFrame frame) {
+        String functionName = frame.getFunctionName();
+        if ("??".equals(functionName)) {    // NOI18N
+            return null;
+        }
+        return createJavaFrame(frame);
+    }
+
+    private DisplayedFrame createJavaFrame(NIFrame frame) {
+        return DisplayedFrame.newBuilder(getDisplayName(frame))
+                .description(getDescription(frame))
+                .line(frame.getLine())
+                .sourceURISupplier(() -> getSourceURI(frame))
+                .build();
+    }
+
+    private static String getDisplayName(NIFrame frame) {
+        String functionName = frame.getFunctionName();
+        String clsMethod;
+        int methodEnd = functionName.indexOf('(');
+        if (methodEnd < 0) {
+            methodEnd = functionName.length();
+        }
+        int methodStart = functionName.lastIndexOf('.', methodEnd);
+        if (methodStart < 0) {
+            clsMethod = functionName.substring(0, methodEnd);
+        } else {
+            int clsStart = functionName.lastIndexOf('.', methodStart - 1);
+            if (clsStart < 0) {
+                clsStart = 0;
+            } else {
+                clsStart++;
+            }
+            clsMethod = functionName.substring(clsStart, methodEnd);
+        }
+        int line = frame.getLine();
+        if (line < 0) {
+            return clsMethod;
+        } else {
+            return clsMethod + ':' + line;
+        }
+    }
+
+    private static String getDescription(NIFrame frame) {
+        String functionName = frame.getFunctionName();
+        int methodEnd = functionName.indexOf('(');
+        if (methodEnd < 0) {
+            methodEnd = functionName.length();
+        }
+        String clsMethod = functionName.substring(0, methodEnd);
+        int line = frame.getLine();
+        if (line < 0) {
+            return clsMethod;
+        } else {
+            return clsMethod + ':' + line;
+        }
+    }
+
+    private URI getSourceURI(NIFrame frame) {
+        String functionName = frame.getFunctionName();
+        int methodEnd = functionName.indexOf('(');
+        if (methodEnd > 0) {
+            int methodStart = functionName.lastIndexOf('.', methodEnd);
+            if (methodStart > 0) {
+                String className = functionName.substring(0, methodStart);
+                URI uri = findClassURI(sourcePath, className);
+                if (uri != null) {
+                    return uri;
+                }
+            }
+        }
+        String fullFileName = frame.getFullFileName();
+        if (fullFileName != null && !fullFileName.isEmpty()) {
+            return new File(fullFileName).toURI();
+        } else {
+            return null;
+        }
+    }
+
+    private static URI findClassURI(ClassPath sourcePath, String className) {
+        String sourceName = className;
+        int i = sourceName.indexOf ('$');
+        if (i > 0) {
+            sourceName = sourceName.substring (0, i);
+        }
+        sourceName = sourceName.replace('.', '/') + ".java";
+        FileObject resource;
+        if (sourcePath != null) {
+            resource = sourcePath.findResource(sourceName);
+        } else {
+            resource = GlobalPathRegistry.getDefault().findResource(sourceName);
+        }
+        if (resource != null) {
+            return resource.toURI();
+        } else {
+            return null;
+        }
+    }
+
+    private static ClassPath findSourcePath(Project project) {
+        if (project != null) {
+            List<FileObject> allSourceRoots = new ArrayList<>();
+            Set<FileObject> preferredRoots = new HashSet<>();
+            Set<FileObject> addedBinaryRoots = new HashSet<>();
+            SourceGroup[] sgs = ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
+            for (SourceGroup sg : sgs) {
+                ClassPath ecp = ClassPath.getClassPath(sg.getRootFolder(), ClassPath.EXECUTE);
+                if (ecp == null) {
+                    ecp = ClassPath.getClassPath(sg.getRootFolder(), ClassPath.SOURCE);
+                }
+                if (ecp != null) {
+                    FileObject[] binaryRoots = ecp.getRoots();
+                    for (FileObject fo : binaryRoots) {
+                        if (addedBinaryRoots.contains(fo)) {
+                            continue;
+                        }
+                        addedBinaryRoots.add(fo);
+                        FileObject[] roots = SourceForBinaryQuery.findSourceRoots(fo.toURL()).getRoots();
+                        for (FileObject fr : roots) {
+                            if (!preferredRoots.contains(fr)) {
+                                allSourceRoots.add(fr);
+                                preferredRoots.add(fr);
+                            }
+                        }
+                    }
+                }
+            }
+            return createClassPath(allSourceRoots);
+        } else {
+            return null;
+        }
+    }
+
+    private static ClassPath createClassPath(Collection<FileObject> froots) {
+        List<PathResourceImplementation> pris = new ArrayList<> ();
+        for (FileObject fo : froots) {
+            if (fo != null && fo.canRead()) {
+                try {
+                    URL url = fo.toURL();
+                    pris.add(ClassPathSupport.createResource(url));
+                } catch (IllegalArgumentException iaex) {
+                    // Can be thrown from ClassPathSupport.createResource()
+                    // Ignore - bad source root
+                }
+            }
+        }
+        return ClassPathSupport.createClassPath(pris);
+    }
+
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java
new file mode 100644
index 0000000..191e2a9
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/displayer/JavaVariablesDisplayer.java
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger.displayer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+
+/**
+ *
+ * @author martin
+ */
+public final class JavaVariablesDisplayer implements VariableDisplayer {
+
+    private static final String HUB = "__hub__";
+    private static final String ARRAY = "__array__";
+    private static final String ARRAY_LENGTH = "__length__";
+    private static final String COMPRESSED_REF_REFIX = "_z_.";
+    private static final String PUBLIC = "public";
+    private static final String STRING_VALUE = "value";
+    private static final String HASH = "hash";
+    private static final String UNSET = "<optimized out>";
+
+    private static final String[] STRING_TYPES = new String[] { String.class.getName(), StringBuilder.class.getName(), StringBuffer.class.getName() };
+
+    private NIDebugger debugger;
+
+    public JavaVariablesDisplayer() {
+    }
+
+    public void setDebugger(NIDebugger debugger) {
+        this.debugger = debugger;
+    }
+
+    @Override
+    public NIVariable[] displayed(NIVariable[] variables) {
+        List<NIVariable> displayedVars = new ArrayList<>(variables.length);
+        for (NIVariable var : variables) {
+           String value = var.getValue();
+            if (UNSET.equals(value)) {
+                continue;
+            }
+            int nch = var.getNumChildren();
+            NIVariable displayedVar;
+            if (nch == 0) {
+                String name = var.getName();
+                if (!name.equals(getNameOrIndex(name))) {
+                    displayedVar = new Var(var);
+                } else {
+                    displayedVar = var;
+                }
+            } else {
+                NIVariable[] children = var.getChildren();
+                NIVariable[] subChildren = children[0].getChildren();
+                // Check for Array
+                if (subChildren.length == 3 &&
+                        //HUB.equals(subChildren[0].getName()) &&
+                        ARRAY_LENGTH.equals(subChildren[1].getName()) &&
+                        ARRAY.equals(subChildren[2].getName())) {
+                    displayedVar = new ArrayVar(var, subChildren[1], subChildren[2]);
+                } else {
+                    // Check for String
+                    String type = getSimpleType(var.getType());
+                    boolean isString = STRING_TYPES[0].equals(type);
+                    boolean likeString = isString;
+                    if (!likeString) {
+                        for (String strType : STRING_TYPES) {
+                            if (strType.equals(type)) {
+                                likeString = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (likeString) {
+                        displayedVar = new StringVar(var, type, isString ? null : subChildren);
+                    } else {
+                        if (children.length == 1 && PUBLIC.equals(children[0].getName())) {
+                            // Object children
+                            displayedVar = new ObjectVar(var, subChildren);
+                        } else {
+                            String name = var.getName();
+                            if (!name.equals(getNameOrIndex(name))) {
+                                displayedVar = new Var(var);
+                            } else {
+                                displayedVar = var;
+                            }
+                        }
+                    }
+                }
+            }
+            displayedVars.add(displayedVar);
+        }
+        return displayedVars.toArray(new NIVariable[displayedVars.size()]);
+    }
+
+    private static String displayType(String type) {
+        if (type.startsWith(COMPRESSED_REF_REFIX)) {
+            type = type.substring(COMPRESSED_REF_REFIX.length());
+        }
+        if (type.endsWith("*")) {
+            type = type.substring(0, type.length() - 1).trim();
+        }
+        return type;
+    }
+
+    private static String getSimpleType(String type) {
+        type = displayType(type);
+        for (int i = 0; i < type.length(); i++) {
+            char c = type.charAt(i);
+            if (c != '.' && !Character.isJavaIdentifierPart(c)) {
+                return type.substring(0, i);
+            }
+        }
+        return type;
+    }
+
+    private static boolean isOfType(String varType, String type) {
+        varType = displayType(varType);
+        return varType.equals(type) || varType.startsWith(type) && !Character.isJavaIdentifierPart(varType.charAt(type.length()));
+    }
+
+    private static int getTypeSize(String type) {
+        type = getSimpleType(type);
+        switch (type) {
+            case "boolean":
+            case "byte":
+                return 1;
+            case "char":
+            case "short":
+                return 2;
+            case "int":
+            case "float":
+                return 4;
+            case "long":
+            case "double":
+                return 8;
+            default:
+                return 8;
+        }
+    }
+
+    private static Map<String, NIVariable> getVarsByName(NIVariable[] vars) {
+        switch (vars.length) {
+            case 0:
+                return Collections.emptyMap();
+            case 1:
+                return Collections.singletonMap(vars[0].getName(), vars[0]);
+            default:
+                Map<String, NIVariable> varsByName = new HashMap<>(vars.length);
+                for (NIVariable var : vars) {
+                    varsByName.put(var.getName(), var);
+                }
+                return varsByName;
+        }
+    }
+
+    private static NIVariable[] restrictChildren(NIVariable[] children, int from, int to) {
+        if (from > 0 || to < children.length) {
+            to = Math.min(to, children.length);
+            if (from < to) {
+                children = Arrays.copyOfRange(children, from, to);
+            } else {
+                children = new NIVariable[]{};
+            }
+        }
+        return children;
+    }
+
+    private static String getHash(NIVariable[] children) {
+        for (NIVariable child : children) {
+            if (HASH.equals(child.getName())) {
+                String hash = child.getValue();
+                try {
+                    hash = Integer.toHexString(Integer.parseInt(hash));
+                } catch (NumberFormatException ex) {}
+                return hash;
+            }
+        }
+        return null;
+    }
+
+    private static String getArrayExpression(NIVariable variable) {
+        StringBuilder arrayExpression = new StringBuilder(variable.getName());
+        while ((variable = variable.getParent()) != null) {
+            if (!PUBLIC.equals(variable.getName())) {
+                arrayExpression.insert(0, '.');
+                arrayExpression.insert(0, variable.getName());
+            }
+        }
+        return arrayExpression.toString();
+    }
+
+    private static String getNameOrIndex(String name) {
+        if (name.endsWith("]")) {
+            int i = name.lastIndexOf(ARRAY+"[");
+            if (i > 0) {
+                String index = name.substring(i + ARRAY.length() + 1, name.length() - 1);
+                return index;
+            }
+        }
+        return name;
+    }
+
+    private String readArray(NIVariable lengthVariable, int itemSize) {
+        int length = Integer.parseInt(lengthVariable.getValue());
+        String expressionPath = lengthVariable.getExpressionPath();
+        if (expressionPath != null && !expressionPath.isEmpty()) {
+            String addressExpr = "&" + expressionPath;
+            return debugger.readMemory(addressExpr, 4, length * itemSize); // length has 4 bytes
+        }
+        return null;
+    }
+
+    private static NIVariable[] getObjectChildren(NIVariable[] children, int from, int to) {
+        for (int i = 0; i < children.length; i++) {
+            if (HUB.equals(children[i].getName())) {
+                NIVariable[] ch1 = Arrays.copyOf(children, i);
+                NIVariable[] ch2 = Arrays.copyOfRange(children, i + 1, children.length);
+                if (ch1.length == 0) {
+                    return ch2;
+                }
+                if (ch2.length == 0) {
+                    return ch1;
+                }
+                NIVariable[] ch = new NIVariable[ch1.length + ch2.length];
+                System.arraycopy(ch1, 0, ch, 0, ch1.length);
+                System.arraycopy(ch2, 0, ch, ch1.length, ch2.length);
+                return ch;
+            }
+        }
+        return restrictChildren(children, from, to);
+    }
+
+    private class StringVar implements NIVariable {
+
+        private final NIVariable var;
+        private final String type;
+        private final NIVariable[] children;
+
+        StringVar(NIVariable var, String type, NIVariable[] children) {
+            this.var = var;
+            this.type = type;
+            this.children = children;
+        }
+
+        @Override
+        public NIFrame getFrame() {
+            return var.getFrame();
+        }
+
+        @Override
+        public String getName() {
+            return getNameOrIndex(var.getName());
+        }
+
+        @Override
+        public String getType() {
+            return type;
+        }
+
+        @Override
+        public String getValue() {
+            NIVariable pub = getVarsByName(var.getChildren()).get(PUBLIC);
+            Map<String, NIVariable> arrayInfo = getVarsByName(getVarsByName(pub.getChildren()).get(STRING_VALUE).getChildren());
+            arrayInfo = getVarsByName(arrayInfo.get(PUBLIC).getChildren());
+            NIVariable arrayVariable = arrayInfo.get(ARRAY);
+            NIVariable lengthVariable = arrayInfo.get(ARRAY_LENGTH);
+            String hexArray = readArray(lengthVariable, 2);
+            if (hexArray != null) {
+                return parseUTF16(hexArray);
+            } else { // legacy code
+                String arrayExpression = getArrayExpression(arrayVariable);
+                int length = Integer.parseInt(lengthVariable.getValue());
+                char[] characters = new char[length];
+                try {
+                    for (int i = 0; i < length; i++) {
+                        NIVariable charVar = debugger.evaluate(arrayExpression + "[" + i + "]", null, var.getFrame());
+                        characters[i] = charVar.getValue().charAt(1);
+                    }
+                } catch (EvaluateException ex) {
+                    return ex.getLocalizedMessage();
+                }
+                return new String(characters);
+            }
+        }
+
+        private String parseUTF16(String hexArray) {
+            CharsetDecoder cd = Charset.forName("utf-16").newDecoder();
+            ByteBuffer buffer = ByteBuffer.allocate(2);
+            int length = hexArray.length() / 4;
+            char[] characters = new char[length];
+            int ih = 0;
+            for (int i = 0; i < length; i++) {
+                byte b1 = parseByte(hexArray, ih);
+                ih += 2;
+                byte b0 = parseByte(hexArray, ih);
+                ih += 2;
+                buffer.rewind();
+                buffer.put(b0);
+                buffer.put(b1);
+                buffer.rewind();
+                try {
+                    char c = cd.decode(buffer).get();
+                    characters[i] = c;
+                } catch (CharacterCodingException ex) {
+                }
+            }
+            return new String(characters);
+        }
+
+        private byte parseByte(String hexArray, int offset) {
+            String hex = new String(new char[] {hexArray.charAt(offset), hexArray.charAt(offset + 1)});
+            return (byte) (Integer.parseInt(hex, 16) & 0xFF);
+        }
+
+        @Override
+        public int getNumChildren() {
+            return children != null ? children.length : 0;
+        }
+
+        @Override
+        public NIVariable[] getChildren(int from, int to) {
+            return children != null ? getObjectChildren(children, from, to) : new NIVariable[]{};
+        }
+
+        @Override
+        public NIVariable getParent() {
+            return var.getParent();
+        }
+
+        @Override
+        public String getExpressionPath() {
+            return var.getExpressionPath();
+        }
+    }
+
+    private class ArrayVar implements NIVariable {
+
+        private final NIVariable var;
+        private final NIVariable lengthVariable;
+        private final int length;
+        private final NIVariable array;
+
+        ArrayVar(NIVariable var, NIVariable lengthVariable, NIVariable array) {
+            this.var = var;
+            this.lengthVariable = lengthVariable;
+            int arrayLength;
+            try {
+                arrayLength = Integer.parseInt(lengthVariable.getValue());
+            } catch (NumberFormatException ex) {
+                arrayLength = 0;
+            }
+            this.length = arrayLength;
+            this.array = array;
+        }
+
+        @Override
+        public NIFrame getFrame() {
+            return var.getFrame();
+        }
+
+        @Override
+        public NIVariable getParent() {
+            return var.getParent();
+        }
+
+        @Override
+        public String getName() {
+            return getNameOrIndex(var.getName());
+        }
+
+        @Override
+        public String getType() {
+            return displayType(var.getType());
+        }
+
+        @Override
+        public String getValue() {
+            String value = var.getValue();
+            if (value.startsWith("@")) {
+                value = getType() + value;
+            }
+            return value + "(length="+length+")";
+        }
+
+        @Override
+        public int getNumChildren() {
+            return length;
+        }
+
+        @Override
+        public NIVariable[] getChildren(int from, int to) {
+            if (from >= 0) {
+                to = Math.min(to, length);
+            } else {
+                from = 0;
+                to = length;
+            }
+            if (from >= to) {
+                return new NIVariable[]{};
+            }
+
+            String arrayAddress = null;
+            String expressionPath = lengthVariable.getExpressionPath();
+            if (expressionPath != null && !expressionPath.isEmpty()) {
+                String addressExpr = "&" + expressionPath;
+                NIVariable addressVariable;
+                try {
+                    addressVariable = debugger.evaluate(addressExpr, null, lengthVariable.getFrame());
+                } catch (EvaluateException ex) {
+                    addressVariable = null;
+                }
+                if (addressVariable != null) {
+                    String address = addressVariable.getValue();
+                    address = address.toLowerCase();
+                    if (address.startsWith("0x")) {
+                        arrayAddress = address;
+                    }
+                }
+            }
+            NIVariable[] elements = new NIVariable[to - from];
+            try {
+                if (arrayAddress != null) {
+                    String itemExpression = "*(" + getSimpleType(getType()) + "*)(" + arrayAddress + "+";
+                    int size = getTypeSize(getType());
+                    int offset = 4 + from*size;
+                    for (int i = from; i < to; i++) {
+                        NIVariable element = debugger.evaluate(itemExpression + offset + ")", Integer.toString(i), var.getFrame());
+                        offset += size;
+                        elements[i - from] = element;
+                    }
+                } else {
+                    String arrayExpression = getArrayExpression(array);
+                    for (int i = from; i < to; i++) {
+                        NIVariable element = debugger.evaluate(arrayExpression + "[" + i + "]", Integer.toString(i), var.getFrame());
+                        elements[i - from] = element;
+                    }
+                }
+            } catch (EvaluateException ex) {
+                return new NIVariable[]{};
+            }
+            return elements;
+        }
+
+        @Override
+        public String getExpressionPath() {
+            return var.getExpressionPath();
+        }
+    }
+
+    private class ObjectVar implements NIVariable {
+
+        private final NIVariable var;
+        private final NIVariable[] children;
+
+        ObjectVar(NIVariable var, NIVariable[] children) {
+            this.var = var;
+            this.children = children;
+        }
+
+        @Override
+        public NIFrame getFrame() {
+            return var.getFrame();
+        }
+
+        @Override
+        public NIVariable getParent() {
+            return var.getParent();
+        }
+
+        @Override
+        public String getName() {
+            return getNameOrIndex(var.getName());
+        }
+
+        @Override
+        public String getType() {
+            return displayType(var.getType());
+        }
+
+        @Override
+        public String getValue() {
+            String value = var.getValue();
+            if (value.startsWith("@") || value.startsWith("0x")) {
+                String hash = getHash(children);
+                if (hash == null) {
+                    if (value.startsWith("@")) {
+                        hash = value.substring(1);
+                    } else {
+                        hash = value.substring(2);
+                    }
+                }
+                value = getType() + '@' + hash;
+            }
+            return value;
+        }
+
+        @Override
+        public int getNumChildren() {
+            return children.length;
+        }
+
+        @Override
+        public NIVariable[] getChildren(int from, int to) {
+            return getObjectChildren(children, from, to);
+        }
+
+        @Override
+        public String getExpressionPath() {
+            return var.getExpressionPath();
+        }
+    }
+
+    private class Var implements NIVariable {
+
+        private final NIVariable var;
+
+        Var(NIVariable var) {
+            this.var = var;
+        }
+
+        @Override
+        public NIFrame getFrame() {
+            return var.getFrame();
+        }
+
+        @Override
+        public NIVariable getParent() {
+            return var.getParent();
+        }
+
+        @Override
+        public String getName() {
+            return getNameOrIndex(var.getName());
+        }
+
+        @Override
+        public String getType() {
+            return var.getType();
+        }
+
+        @Override
+        public String getValue() {
+            return var.getValue();
+        }
+
+        @Override
+        public int getNumChildren() {
+            return var.getNumChildren();
+        }
+
+        @Override
+        public NIVariable[] getChildren(int from, int to) {
+            return var.getChildren(from, to);
+        }
+
+        @Override
+        public NIVariable[] getChildren() {
+            return var.getChildren();
+        }
+
+        @Override
+        public String getExpressionPath() {
+            return var.getExpressionPath();
+        }
+    }
+}
diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/resources/mf-layer.xml b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/resources/mf-layer.xml
new file mode 100644
index 0000000..73421fc
--- /dev/null
+++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/resources/mf-layer.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.0//EN" "http://www.netbeans.org/dtds/filesystem-1_0.dtd">
+<filesystem>
+
+    <folder name="Editors">
+        <folder name="text">
+        </folder>
+        <folder name="AnnotationTypes">
+        </folder>
+    </folder>
+
+</filesystem>
diff --git a/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/NIDebugRunnerTest.java b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/NIDebugRunnerTest.java
new file mode 100644
index 0000000..7ed9276
--- /dev/null
+++ b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/NIDebugRunnerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.debugger.jpda.LineBreakpoint;
+import org.netbeans.modules.java.nativeimage.debugger.api.NIDebugRunner;
+import org.netbeans.modules.nativeimage.api.debug.EvaluateException;
+import org.netbeans.modules.nativeimage.api.debug.NIDebugger;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
+
+/**
+ *
+ * @author martin
+ */
+public class NIDebugRunnerTest {
+
+    public NIDebugRunnerTest() {
+    }
+
+    private static Lookup getTestLookup() {
+        Lookup launchCtx = new ProxyLookup(
+                        Lookups.fixed(new TestNIDebuggerServiceProvider()),
+                        Lookup.getDefault()
+                );
+        return launchCtx;
+    }
+
+    @Test
+    public void testDebuggerProviderBreakpoints() {
+        LineBreakpoint bp1 = LineBreakpoint.create("file:///testFile", 10);
+        DebuggerManager.getDebuggerManager().addBreakpoint(bp1);
+        Lookups.executeWith(getTestLookup(), () -> {
+            NIDebugger debugger = NIDebugRunner.start(new File("NIFile"), Arrays.asList("ARG1", "ARG2"), "MI", null, "displayName", null, engine -> {});
+            try {
+                NIVariable result = debugger.evaluate("breakpoints", null, null);
+                assertEquals(1, result.getChildren().length);
+                assertEquals("/testFile:10", result.getChildren()[0].getValue());
+            } catch (EvaluateException ex) {
+                throw new AssertionError(ex.getLocalizedMessage(), ex);
+            }
+        });
+        DebuggerManager.getDebuggerManager().removeBreakpoint(bp1);
+    }
+
+    @Test
+    public void testVersion() {
+        Lookups.executeWith(getTestLookup(), () -> {
+            NIDebugger debugger = NIDebugRunner.start(new File("NIFile"), Collections.emptyList(), "MI", null, "displayName", null, engine -> {});
+            assertEquals("Test1", debugger.getVersion());
+        });
+    }
+}
diff --git a/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerProvider.java b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerProvider.java
new file mode 100644
index 0000000..a514ddf
--- /dev/null
+++ b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerProvider.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.java.nativeimage.debugger;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import org.netbeans.api.debugger.Breakpoint;
+import org.netbeans.api.debugger.DebuggerEngine;
+
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer;
+import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer;
+
+public class TestNIDebuggerProvider implements NIDebuggerProvider {
+
+    private final Map<Object, Breakpoint> breakpoints = new LinkedHashMap<>();
+    private FrameDisplayer frameDisplayer;
+    private VariableDisplayer variablesDisplayer;
+
+    public TestNIDebuggerProvider() {
+    }
+
+    @Override
+    public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor) {
+        Breakpoint nativeBreakpoint = new Breakpoint() {
+            @Override
+            public boolean isEnabled() {
+                return breakpointDescriptor.isEnabled();
+            }
+
+            @Override
+            public void disable() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void enable() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public String toString() {
+                return breakpointDescriptor.getFilePath() + ':' + breakpointDescriptor.getLine();
+            }
+        };
+        breakpoints.put(id, nativeBreakpoint);
+        return nativeBreakpoint;
+    }
+
+    @Override
+    public void removeBreakpoint(Object id) {
+        breakpoints.remove(id);
+    }
+
+    @Override
+    public void setFrameDisplayer(FrameDisplayer frameDisplayer) {
+        this.frameDisplayer = frameDisplayer;
+    }
+
+    @Override
+    public void setVariablesDisplayer(VariableDisplayer variablesDisplayer) {
+        this.variablesDisplayer = variablesDisplayer;
+    }
+
+    @Override
+    public CompletableFuture<Void> start(List<String> command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer<DebuggerEngine> startedEngine) {
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public CompletableFuture<NIVariable> evaluateAsync(String expression, String resultName, NIFrame frame) {
+        NIVariable result;
+        if ("breakpoints".equals(expression)) {
+            NIVariable[] children = new NIVariable[breakpoints.size()];
+            result = new TestNIVariable(expression, "BP", "BP", null, children, null);
+            int i = 0;
+            for (Breakpoint b : breakpoints.values()) {
+                children[i++] = new TestNIVariable("b" + i, "BP", b.toString(), result, new NIVariable[]{}, null);
+            }
+        } else {
+            result = new TestNIVariable(expression, "type", "value", null, new NIVariable[]{}, null);
+        }
+        result = variablesDisplayer.displayed(result)[0];
+        return CompletableFuture.completedFuture(result);
+    }
+
+    @Override
+    public String readMemory(String address, long offset, int length) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String getVersion() {
+        return "Test1";
+    }
+
+}
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerServiceProvider.java
similarity index 67%
rename from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
rename to java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerServiceProvider.java
index 0fc9c76..7735bae 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/EvaluateException.java
+++ b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIDebuggerServiceProvider.java
@@ -16,17 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.java.nativeimage.debugger;
 
-public final class EvaluateException extends Exception {
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider;
+import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerServiceProvider;
 
-    /**
-     * Constructs an instance of <code>EvaluateException</code> with the
-     * specified detail message.
-     *
-     * @param msg the detail message.
-     */
-    EvaluateException(String msg) {
-        super(msg);
+public class TestNIDebuggerServiceProvider implements NIDebuggerServiceProvider {
+
+    @Override
+    public NIDebuggerProvider create() {
+        return new TestNIDebuggerProvider();
     }
 }
diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIVariable.java
similarity index 50%
copy from cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java
copy to java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIVariable.java
index cd5c6fe..de5bde1 100644
--- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPVariable.java
+++ b/java/java.nativeimage.debugger/test/unit/src/org/netbeans/modules/java/nativeimage/debugger/TestNIVariable.java
@@ -16,66 +16,67 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.netbeans.modules.cpplite.debugger;
+package org.netbeans.modules.java.nativeimage.debugger;
 
-import java.util.Map;
-import java.util.Objects;
+import org.netbeans.modules.nativeimage.api.debug.NIFrame;
+import org.netbeans.modules.nativeimage.api.debug.NIVariable;
 
-import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst;
-import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue;
+public class TestNIVariable implements NIVariable {
 
-/**
- * Representation of a variable.
- */
-public final class CPPVariable {
-
-    private final CPPFrame frame;
-    private final String uniqueName;
     private final String name;
     private final String type;
     private final String value;
-    private final int numChildren;
-    private volatile Map<String, CPPVariable> children;
+    private final NIVariable parent;
+    private final NIVariable[] children;
+    private final NIFrame frame;
 
-    CPPVariable(CPPFrame frame, String uniqueName, String name, String type, MIValue value, int numChildren) {
-        this.frame = frame;
-        this.uniqueName = uniqueName;
+    TestNIVariable(String name, String type, String value, NIVariable parent, NIVariable[] children, NIFrame frame) {
         this.name = name;
         this.type = type;
-        this.value = (value instanceof MIConst) ? ((MIConst) value).value() : Objects.toString(value);
-        this.numChildren = numChildren;
-    }
-
-    public String getUniqueName() {
-        return uniqueName;
+        this.value = value;
+        this.parent = parent;
+        this.children = children;
+        this.frame = frame;
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    @Override
     public String getType() {
         return type;
     }
 
+    @Override
     public String getValue() {
         return value;
     }
 
+    @Override
+    public NIVariable getParent() {
+        return parent;
+    }
+
+    @Override
     public int getNumChildren() {
-        return numChildren;
+        return children.length;
+    }
+
+    @Override
+    public NIVariable[] getChildren(int from, int to) {
+        return children;
     }
 
-    public Map<String, CPPVariable> getChildrenVariables() {
-        Map<String, CPPVariable> vars = children;
-        if (vars == null) {
-            synchronized (this) {
-                vars = children;
-                if (vars == null) {
-                    children = vars = CPPFrame.retrieveVariables(frame, this);
-                }
-            }
-        }
-        return vars;
+    @Override
+    public NIFrame getFrame() {
+        return frame;
     }
+
+    @Override
+    public String getExpressionPath() {
+        return "";
+    }
+
 }
diff --git a/nbbuild/build.properties b/nbbuild/build.properties
index fc358a4..759f93a 100644
--- a/nbbuild/build.properties
+++ b/nbbuild/build.properties
@@ -211,6 +211,7 @@ config.javadoc.friend=\
     java.j2seproject,\
     junit,\
     lib.v8debug,\
+    nativeimage.api,\
     versioning.core,\
     masterfs,\
     masterfs.ui,\
diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties
index 4d447b1..7a93cd4 100644
--- a/nbbuild/cluster.properties
+++ b/nbbuild/cluster.properties
@@ -454,6 +454,7 @@ nb.cluster.ide=\
         lsp.client,\
         mercurial,\
         mylyn.util,\
+        nativeimage.api,\
         notifications,\
         o.apache.commons.httpclient,\
         o.apache.commons.lang,\
@@ -663,6 +664,7 @@ nb.cluster.java=\
         java.metrics,\
         java.module.graph,\
         java.mx.project,\
+        java.nativeimage.debugger,\
         java.navigation,\
         java.openjdk.project,\
         java.platform,\
diff --git a/nbbuild/javadoctools/links.xml b/nbbuild/javadoctools/links.xml
index 34a518f..5d07aaa 100644
--- a/nbbuild/javadoctools/links.xml
+++ b/nbbuild/javadoctools/links.xml
@@ -240,3 +240,4 @@
 <link href="${javadoc.docs.org-netbeans-modules-java-editor-lib}" offline="true" packagelistloc="${netbeans.javadoc.dir}/org-netbeans-modules-java-editor-lib"/>
 <link href="${javadoc.docs.org-netbeans-modules-web-common}" offline="true" packagelistloc="${netbeans.javadoc.dir}/org-netbeans-modules-web-common"/>
 <link href="${javadoc.docs.org-netbeans-modules-db-core}" offline="true" packagelistloc="${netbeans.javadoc.dir}/org-netbeans-modules-db-core"/>
+<link href="${javadoc.docs.org-netbeans-modules-nativeimage-api}" offline="true" packagelistloc="${netbeans.javadoc.dir}/org-netbeans-modules-nativeimage-api"/>
diff --git a/nbbuild/javadoctools/properties.xml b/nbbuild/javadoctools/properties.xml
index 6f1abb8..3c633d1 100644
--- a/nbbuild/javadoctools/properties.xml
+++ b/nbbuild/javadoctools/properties.xml
@@ -237,3 +237,4 @@
 <property name="javadoc.docs.org-netbeans-modules-java-editor-lib" value="${javadoc.web.root}/org-netbeans-modules-java-editor-lib"/>
 <property name="javadoc.docs.org-netbeans-modules-web-common" value="${javadoc.web.root}/org-netbeans-modules-web-common"/>
 <property name="javadoc.docs.org-netbeans-modules-db-core" value="${javadoc.web.root}/org-netbeans-modules-db-core"/>
+<property name="javadoc.docs.org-netbeans-modules-nativeimage-api" value="${javadoc.web.root}/org-netbeans-modules-nativeimage-api"/>
diff --git a/nbbuild/javadoctools/replaces.xml b/nbbuild/javadoctools/replaces.xml
index ce34768..f7e27e8 100644
--- a/nbbuild/javadoctools/replaces.xml
+++ b/nbbuild/javadoctools/replaces.xml
@@ -237,3 +237,4 @@
 <replacefilter token="@org-netbeans-modules-java-editor-lib@" value="${javadoc.docs.org-netbeans-modules-java-editor-lib}"/>
 <replacefilter token="@org-netbeans-modules-web-common@" value="${javadoc.docs.org-netbeans-modules-web-common}"/>
 <replacefilter token="@org-netbeans-modules-db-core@" value="${javadoc.docs.org-netbeans-modules-db-core}"/>
+<replacefilter token="@org-netbeans-modules-nativeimage-api@" value="${javadoc.docs.org-netbeans-modules-nativeimage-api}"/>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists