diff --git a/ChangeLog b/ChangeLog
index 5421f5b8..c3a1948a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2022-09-13  Aleksei Vetrov  <vvvvvv@google.com>
+
+	* NEWS (libdwfl): Add dwfl_report_offline_memory.
+
 2022-04-28  Di Chen  <dichen@redhat.com>
 
 	* NEWS: Add readefl -D, --use-dynamic.
diff --git a/libdw/libdw.map b/libdw/libdw.map
index 8f393438..ff3b7d20 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -137,6 +137,7 @@ ELFUTILS_0.122 {
     dwfl_report_end;
     dwfl_report_module;
     dwfl_report_offline;
+    dwfl_report_offline_memory;
     dwfl_standard_argp;
     dwfl_standard_find_debuginfo;
     dwfl_version;
diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h
index edb537c2..9114f7f0 100644
--- a/libdwfl/libdwfl.h
+++ b/libdwfl/libdwfl.h
@@ -159,6 +159,10 @@ extern Dwfl_Module *dwfl_report_elf (Dwfl *dwfl, const char *name,
 extern Dwfl_Module *dwfl_report_offline (Dwfl *dwfl, const char *name,
 					 const char *file_name, int fd);
 
+/* Similar, but report ELF from memory region.  */
+extern Dwfl_Module *dwfl_report_offline_memory (Dwfl *dwfl, const char *name,
+						const char *file_name,
+						char *data, size_t size);
 
 /* Finish reporting the current set of modules to the library.
    If REMOVED is not null, it's called for each module that
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index a2949e74..011b5de9 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -631,6 +631,11 @@ extern Dwfl_Error __libdw_open_file (int *fdp, Elf **elfp,
 				     bool close_on_fail, bool archive_ok)
   internal_function;
 
+/* Same as __libdw_open_file, but opens Elf handle from memory region.  */
+extern Dwfl_Error __libdw_open_elf_memory (char *data, size_t size, Elf **elfp,
+					   bool archive_ok)
+  internal_function;
+
 /* Same as __libdw_open_file, but never closes the given file
    descriptor and ELF_K_AR is always an acceptable type.  */
 extern Dwfl_Error __libdw_open_elf (int fd, Elf **elfp) internal_function;
@@ -760,6 +765,7 @@ INTDECL (dwfl_report_begin_add)
 INTDECL (dwfl_report_module)
 INTDECL (dwfl_report_segment)
 INTDECL (dwfl_report_offline)
+INTDECL (dwfl_report_offline_memory)
 INTDECL (dwfl_report_end)
 INTDECL (dwfl_build_id_find_elf)
 INTDECL (dwfl_build_id_find_debuginfo)
diff --git a/libdwfl/offline.c b/libdwfl/offline.c
index 58ba4c36..499663e3 100644
--- a/libdwfl/offline.c
+++ b/libdwfl/offline.c
@@ -1,6 +1,7 @@
 /* Recover relocatibility for addresses computed from debug information.
    Copyright (C) 2005-2009, 2012 Red Hat, Inc.
    Copyright (C) 2022 Mark J. Wielaard <mark@klomp.org>
+   Copyright (C) 2022 Google LLC
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -252,6 +253,7 @@ process_archive (Dwfl *dwfl, const char *name, const char *file_name, int fd,
 
 {
   Dwfl_Module *mod = NULL;
+  /* elf_begin supports opening archives even with fd == -1 passed.  */
   Elf *member = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, archive);
   if (unlikely (member == NULL)) /* Empty archive.  */
     {
@@ -320,3 +322,27 @@ dwfl_report_offline (Dwfl *dwfl, const char *name,
   return __libdwfl_report_offline (dwfl, name, file_name, fd, closefd, NULL);
 }
 INTDEF (dwfl_report_offline)
+
+Dwfl_Module *
+dwfl_report_offline_memory (Dwfl *dwfl, const char *name,
+			    const char *file_name, char *data, size_t size)
+{
+  if (dwfl == NULL)
+    return NULL;
+
+  Elf *elf;
+  Dwfl_Error error = __libdw_open_elf_memory (data, size, &elf, true);
+  if (error != DWFL_E_NOERROR)
+    {
+      __libdwfl_seterrno (error);
+      return NULL;
+    }
+  /* It is ok to pass fd == -1 here, because libelf uses it as a value for
+     "no file opened" and supports working with files without fd, thanks to
+     the existence of the elf_memory function.  */
+  Dwfl_Module *mod = process_file (dwfl, name, file_name, -1, elf, NULL);
+  if (mod == NULL)
+    elf_end (elf);
+  return mod;
+}
+INTDEF (dwfl_report_offline_memory)
diff --git a/libdwfl/open.c b/libdwfl/open.c
index 77bd2bd9..da8b59a3 100644
--- a/libdwfl/open.c
+++ b/libdwfl/open.c
@@ -1,5 +1,6 @@
 /* Decompression support for libdwfl: zlib (gzip), bzlib (bzip2) or lzma (xz).
    Copyright (C) 2009, 2016 Red Hat, Inc.
+   Copyright (C) 2022 Google LLC
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -53,6 +54,9 @@ static Dwfl_Error
 decompress (int fd __attribute__ ((unused)), Elf **elf)
 {
   Dwfl_Error error = DWFL_E_BADELF;
+  /* ELF cannot be decompressed, if there is no file descriptor.  */
+  if (fd == -1)
+    return error;
   void *buffer = NULL;
   size_t size = 0;
 
@@ -124,11 +128,12 @@ what_kind (int fd, Elf **elfp, Elf_Kind *kind, bool *may_close_fd)
 
 static Dwfl_Error
 libdw_open_elf (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok,
-		bool never_close_fd, bool bad_elf_ok)
+		bool never_close_fd, bool bad_elf_ok, bool use_elfp)
 {
   bool may_close_fd = false;
 
-  Elf *elf = elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
+  Elf *elf =
+      use_elfp ? *elfp : elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
 
   Elf_Kind kind;
   Dwfl_Error error = what_kind (*fdp, &elf, &kind, &may_close_fd);
@@ -194,11 +199,28 @@ libdw_open_elf (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok,
 Dwfl_Error internal_function
 __libdw_open_file (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok)
 {
-  return libdw_open_elf (fdp, elfp, close_on_fail, archive_ok, false, false);
+  return libdw_open_elf (fdp, elfp, close_on_fail, archive_ok, false, false,
+			 false);
+}
+
+Dwfl_Error internal_function
+__libdw_open_elf_memory (char *data, size_t size, Elf **elfp, bool archive_ok)
+{
+  /* It is ok to use `fd == -1` here, because libelf uses it as a value for
+     "no file opened" and code supports working with this value, and also
+     `never_close_fd == false` is passed to prevent closing non-existant file.
+     The only caveat is in `decompress` method, which doesn't support
+     decompressing from memory, so reading compressed zImage using this method
+     won't work.  */
+  int fd = -1;
+  *elfp = elf_memory (data, size);
+  /* Allow using this ELF as reference for subsequent elf_begin calls.  */
+  (*elfp)->cmd = ELF_C_READ_MMAP_PRIVATE;
+  return libdw_open_elf (&fd, elfp, false, archive_ok, true, false, true);
 }
 
 Dwfl_Error internal_function
 __libdw_open_elf (int fd, Elf **elfp)
 {
-  return libdw_open_elf (&fd, elfp, false, true, true, true);
+  return libdw_open_elf (&fd, elfp, false, true, true, true, false);
 }
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 87988fb9..0cd98add 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -47,6 +47,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
 		  alldts typeiter typeiter2 low_high_pc \
 		  test-elf_cntl_gelf_getshdr dwflsyms dwfllines \
 		  dwfl-report-elf-align dwfl-report-segment-contiguous \
+		  dwfl-report-offline-memory \
 		  varlocs backtrace backtrace-child \
 		  backtrace-data backtrace-dwarf debuglink debugaltlink \
 		  buildid deleted deleted-lib.so aggregate_size peel_type \
@@ -148,6 +149,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
 	run-readelf-mixed-corenote.sh run-dwfllines.sh \
 	run-readelf-variant.sh run-readelf-fat-lto.sh \
 	run-dwfl-report-elf-align.sh run-addr2line-test.sh \
+	run-dwfl-report-offline-memory.sh \
 	run-addr2line-i-test.sh run-addr2line-i-lex-test.sh \
 	run-addr2line-i-demangle-test.sh run-addr2line-alt-debugpath.sh \
 	run-varlocs.sh run-exprlocs.sh run-varlocs-vars.sh run-funcretval.sh \
@@ -411,6 +413,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
 	     testfile69.core.bz2 testfile69.so.bz2 \
 	     testfile70.core.bz2 testfile70.exec.bz2 testfile71.bz2 \
 	     run-dwfllines.sh run-dwfl-report-elf-align.sh \
+	     run-dwfl-report-offline-memory.sh \
 	     testfile-dwfl-report-elf-align-shlib.so.bz2 \
 	     testfilenolines.bz2 test-core-lib.so.bz2 test-core.core.bz2 \
 	     test-core.exec.bz2 run-addr2line-test.sh \
@@ -709,6 +712,7 @@ test_elf_cntl_gelf_getshdr_LDADD = $(libelf)
 dwflsyms_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 dwfllines_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 dwfl_report_elf_align_LDADD = $(libdw)
+dwfl_report_offline_memory_LDADD = $(libdw)
 dwfl_report_segment_contiguous_LDADD = $(libdw) $(libebl) $(libelf)
 varlocs_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 backtrace_LDADD = $(libdw) $(libelf) $(argp_LDADD)
diff --git a/tests/dwfl-report-offline-memory.c b/tests/dwfl-report-offline-memory.c
new file mode 100644
index 00000000..837aca5e
--- /dev/null
+++ b/tests/dwfl-report-offline-memory.c
@@ -0,0 +1,97 @@
+/* Test program for dwfl_report_offline_memory.
+   Copyright (C) 2022 Google LLC
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <assert.h>
+#include <config.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include ELFUTILS_HEADER(dwfl)
+#include "system.h"
+
+
+static const Dwfl_Callbacks offline_callbacks =
+  {
+    .find_debuginfo = INTUSE(dwfl_standard_find_debuginfo),
+    .section_address = INTUSE(dwfl_offline_section_address),
+  };
+
+static int
+count_modules (Dwfl_Module *mod __attribute__ ((unused)),
+	       void **userdata __attribute__ ((unused)),
+	       const char *name __attribute__ ((unused)),
+	       Dwarf_Addr base __attribute__ ((unused)), void *arg)
+{
+  unsigned long long *counter = arg;
+  ++(*counter);
+  return DWARF_CB_OK;
+}
+
+int
+main (int argc, char **argv)
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  if (argc != 3)
+    error (-1, 0,
+	   "usage: dwfl_report_offline_memory [filename] "
+	   "[expected number of modules]");
+
+  const char *fname = argv[1];
+  int fd = open (fname, O_RDONLY);
+  if (fd < 0)
+    error (-1, 0, "can't open file %s: %s", fname, strerror (errno));
+  size_t size = lseek (fd, 0, SEEK_END);
+  lseek (fd, 0, SEEK_SET);
+  char *data = malloc (size);
+  size_t bytes_read = read (fd, data, size);
+  assert (bytes_read == size);
+  close (fd);
+
+  Dwfl *dwfl = dwfl_begin (&offline_callbacks);
+  assert (dwfl != NULL);
+
+  Dwfl_Module *mod =
+      dwfl_report_offline_memory (dwfl, argv[1], argv[1], data, size);
+  assert (mod != NULL);
+  dwfl_report_end (dwfl, NULL, NULL);
+
+  unsigned long long number_of_modules = 0;
+  ptrdiff_t offset =
+      dwfl_getmodules (dwfl, &count_modules, &number_of_modules, 0);
+  if (offset < 0)
+    error (1, 0, "dwfl_getmodules: %s", dwfl_errmsg (-1));
+  assert (offset == 0);
+
+  char *endptr;
+  unsigned long long expected_number_of_modules =
+      strtoull (argv[2], &endptr, 0);
+  assert (endptr && !*endptr);
+  assert (number_of_modules == expected_number_of_modules);
+
+  dwfl_end (dwfl);
+  free (data);
+
+  return 0;
+}
diff --git a/tests/run-dwfl-report-offline-memory.sh b/tests/run-dwfl-report-offline-memory.sh
new file mode 100755
index 00000000..644a45dc
--- /dev/null
+++ b/tests/run-dwfl-report-offline-memory.sh
@@ -0,0 +1,26 @@
+#! /bin/sh
+# Copyright (C) 2022 Google LLC
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+testfiles testfile-dwfl-report-elf-align-shlib.so
+testfiles testarchive64.a
+
+testrun ${abs_builddir}/dwfl-report-offline-memory ./testfile-dwfl-report-elf-align-shlib.so 1
+testrun ${abs_builddir}/dwfl-report-offline-memory ./testarchive64.a 3
+
+exit 0
