Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
2ebe51beb65be34551f54036f909ed45ad45ccd1
[simgrid.git] / src / mc / sosp / RemoteProcessMemory.cpp
1 /* Copyright (c) 2014-2023. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #define _FILE_OFFSET_BITS 64 /* needed for pread_whole to work as expected on 32bits */
7
8 #include "src/mc/sosp/RemoteProcessMemory.hpp"
9
10 #include "src/mc/sosp/Snapshot.hpp"
11 #include "xbt/file.hpp"
12 #include "xbt/log.h"
13
14 #include <fcntl.h>
15 #include <libunwind-ptrace.h>
16 #include <sys/mman.h> // PROT_*
17
18 #include <algorithm>
19 #include <cerrno>
20 #include <cstring>
21 #include <memory>
22 #include <mutex>
23 #include <string>
24 #include <string_view>
25
26 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(mc_process, mc, "MC process information");
27
28 namespace simgrid::mc {
29
30 // ***** Helper stuff
31
32 static bool is_filtered_lib(std::string_view libname)
33 {
34   return libname != "libsimgrid";
35 }
36
37 static std::string get_lib_name(const std::string& pathname)
38 {
39   std::string map_basename = simgrid::xbt::Path(pathname).get_base_name();
40   std::string libname;
41
42   if (size_t pos = map_basename.rfind(".so"); pos != std::string::npos) {
43     // strip the extension (matching regex "\.so.*$")
44     libname.assign(map_basename, 0, pos);
45
46     // strip the version suffix (matching regex "-[.0-9-]*$")
47     while (true) {
48       pos = libname.rfind('-');
49       if (pos == std::string::npos || libname.find_first_not_of(".0123456789", pos + 1) != std::string::npos)
50         break;
51       libname.erase(pos);
52     }
53   }
54
55   return libname;
56 }
57
58 static ssize_t pread_whole(int fd, void* buf, size_t count, off_t offset)
59 {
60   auto* buffer       = static_cast<char*>(buf);
61   ssize_t real_count = count;
62   while (count) {
63     ssize_t res = pread(fd, buffer, count, offset);
64     if (res > 0) {
65       count -= res;
66       buffer += res;
67       offset += res;
68     } else if (res == 0)
69       return -1;
70     else if (errno != EINTR) {
71       XBT_ERROR("pread_whole: %s", strerror(errno));
72       return -1;
73     }
74   }
75   return real_count;
76 }
77
78 static ssize_t pwrite_whole(int fd, const void* buf, size_t count, off_t offset)
79 {
80   const auto* buffer = static_cast<const char*>(buf);
81   ssize_t real_count = count;
82   while (count) {
83     ssize_t res = pwrite(fd, buffer, count, offset);
84     if (res > 0) {
85       count -= res;
86       buffer += res;
87       offset += res;
88     } else if (res == 0)
89       return -1;
90     else if (errno != EINTR) {
91       XBT_ERROR("pwrite_whole: %s", strerror(errno));
92       return -1;
93     }
94   }
95   return real_count;
96 }
97
98 int open_vm(pid_t pid, int flags)
99 {
100   std::string buffer = "/proc/" + std::to_string(pid) + "/mem";
101   return open(buffer.c_str(), flags);
102 }
103
104 // ***** RemoteProcessMemory
105
106 RemoteProcessMemory::RemoteProcessMemory(pid_t pid) : AddressSpace(this), pid_(pid), running_(true) {}
107
108 void RemoteProcessMemory::init(xbt_mheap_t mmalloc_default_mdp)
109 {
110   this->heap_address = remote(mmalloc_default_mdp);
111
112   this->memory_map_ = simgrid::xbt::get_memory_map(this->pid_);
113   this->init_memory_map_info();
114
115   int fd = open_vm(this->pid_, O_RDWR);
116   xbt_assert(fd >= 0, "Could not open file for process virtual address space");
117   this->memory_file = fd;
118
119   this->unw_addr_space            = simgrid::mc::UnwindContext::createUnwindAddressSpace();
120   this->unw_underlying_addr_space = simgrid::unw::create_addr_space();
121   this->unw_underlying_context    = simgrid::unw::create_context(this->unw_underlying_addr_space, this->pid_);
122
123   auto ignored_local_variables = {
124       std::make_pair("e", "*"),
125       std::make_pair("_log_ev", "*"),
126
127       /* Ignore local variable about time used for tracing */
128       std::make_pair("start_time", "*"),
129   };
130   for (auto const& [var, frame] : ignored_local_variables)
131     ignore_local_variable(var, frame);
132
133   ignore_global_variable("counter"); // Static variable used for tracing
134 }
135
136 RemoteProcessMemory::~RemoteProcessMemory()
137 {
138   if (this->memory_file >= 0)
139     close(this->memory_file);
140
141   if (this->unw_underlying_addr_space != unw_local_addr_space) {
142     if (this->unw_underlying_addr_space)
143       unw_destroy_addr_space(this->unw_underlying_addr_space);
144     if (this->unw_underlying_context)
145       _UPT_destroy(this->unw_underlying_context);
146   }
147
148   unw_destroy_addr_space(this->unw_addr_space);
149 }
150
151 /** Refresh the information about the process
152  *
153  *  Do not use directly, this is used by the getters when appropriate
154  *  in order to have fresh data.
155  */
156 void RemoteProcessMemory::refresh_heap()
157 {
158   // Read/dereference/refresh the std_heap pointer:
159   this->read(this->heap.get(), this->heap_address);
160   this->cache_flags_ |= RemoteProcessMemory::cache_heap;
161 }
162
163 /** Refresh the information about the process
164  *
165  *  Do not use directly, this is used by the getters when appropriate
166  *  in order to have fresh data.
167  * */
168 void RemoteProcessMemory::refresh_malloc_info()
169 {
170   // Refresh process->heapinfo:
171   if (this->cache_flags_ & RemoteProcessMemory::cache_malloc)
172     return;
173   size_t count = this->heap->heaplimit + 1;
174   if (this->heap_info.size() < count)
175     this->heap_info.resize(count);
176   this->read_bytes(this->heap_info.data(), count * sizeof(malloc_info), remote(this->heap->heapinfo));
177   this->cache_flags_ |= RemoteProcessMemory::cache_malloc;
178 }
179 std::size_t RemoteProcessMemory::get_remote_heap_bytes()
180 {
181   return mmalloc_get_bytes_used_remote(get_heap()->heaplimit, get_malloc_info());
182 }
183
184 /** @brief Finds the range of the different memory segments and binary paths */
185 void RemoteProcessMemory::init_memory_map_info()
186 {
187   XBT_DEBUG("Get debug information ...");
188   this->maestro_stack_start_ = nullptr;
189   this->maestro_stack_end_   = nullptr;
190   this->object_infos.clear();
191   this->binary_info = nullptr;
192
193   std::vector<simgrid::xbt::VmMap> const& maps = this->memory_map_;
194
195   const char* current_name = nullptr;
196
197   for (size_t i = 0; i < maps.size(); i++) {
198     simgrid::xbt::VmMap const& reg = maps[i];
199     const char* pathname           = maps[i].pathname.c_str();
200
201     // Nothing to do
202     if (maps[i].pathname.empty()) {
203       current_name = nullptr;
204       continue;
205     }
206
207     // [stack], [vvar], [vsyscall], [vdso] ...
208     if (pathname[0] == '[') {
209       if ((reg.prot & PROT_WRITE) && not memcmp(pathname, "[stack]", 7)) {
210         this->maestro_stack_start_ = remote(reg.start_addr);
211         this->maestro_stack_end_   = remote(reg.end_addr);
212       }
213       current_name = nullptr;
214       continue;
215     }
216
217     if (current_name && strcmp(current_name, pathname) == 0)
218       continue;
219
220     current_name = pathname;
221     if (not(reg.prot & PROT_READ) && (reg.prot & PROT_EXEC))
222       continue;
223
224     const bool is_executable = not i;
225     std::string libname;
226     if (not is_executable) {
227       libname = get_lib_name(pathname);
228       if (is_filtered_lib(libname)) {
229         continue;
230       }
231     }
232
233     std::shared_ptr<simgrid::mc::ObjectInformation> info =
234         simgrid::mc::createObjectInformation(this->memory_map_, pathname);
235     this->object_infos.push_back(info);
236     if (is_executable)
237       this->binary_info = info;
238   }
239
240   xbt_assert(this->maestro_stack_start_, "Did not find maestro_stack_start");
241   xbt_assert(this->maestro_stack_end_, "Did not find maestro_stack_end");
242
243   XBT_DEBUG("Get debug information done !");
244 }
245
246 std::shared_ptr<simgrid::mc::ObjectInformation> RemoteProcessMemory::find_object_info(RemotePtr<void> addr) const
247 {
248   for (auto const& object_info : this->object_infos)
249     if (addr.address() >= (std::uint64_t)object_info->start && addr.address() <= (std::uint64_t)object_info->end)
250       return object_info;
251   return nullptr;
252 }
253
254 std::shared_ptr<ObjectInformation> RemoteProcessMemory::find_object_info_exec(RemotePtr<void> addr) const
255 {
256   for (std::shared_ptr<ObjectInformation> const& info : this->object_infos)
257     if (addr.address() >= (std::uint64_t)info->start_exec && addr.address() <= (std::uint64_t)info->end_exec)
258       return info;
259   return nullptr;
260 }
261
262 std::shared_ptr<ObjectInformation> RemoteProcessMemory::find_object_info_rw(RemotePtr<void> addr) const
263 {
264   for (std::shared_ptr<ObjectInformation> const& info : this->object_infos)
265     if (addr.address() >= (std::uint64_t)info->start_rw && addr.address() <= (std::uint64_t)info->end_rw)
266       return info;
267   return nullptr;
268 }
269
270 simgrid::mc::Frame* RemoteProcessMemory::find_function(RemotePtr<void> ip) const
271 {
272   std::shared_ptr<simgrid::mc::ObjectInformation> info = this->find_object_info_exec(ip);
273   return info ? info->find_function((void*)ip.address()) : nullptr;
274 }
275
276 /** Find (one occurrence of) the named variable definition
277  */
278 const simgrid::mc::Variable* RemoteProcessMemory::find_variable(const char* name) const
279 {
280   // First lookup the variable in the executable shared object.
281   // A global variable used directly by the executable code from a library
282   // is reinstantiated in the executable memory .data/.bss.
283   // We need to look up the variable in the executable first.
284   if (this->binary_info) {
285     std::shared_ptr<simgrid::mc::ObjectInformation> const& info = this->binary_info;
286     const simgrid::mc::Variable* var                            = info->find_variable(name);
287     if (var)
288       return var;
289   }
290
291   for (std::shared_ptr<simgrid::mc::ObjectInformation> const& info : this->object_infos) {
292     const simgrid::mc::Variable* var = info->find_variable(name);
293     if (var)
294       return var;
295   }
296
297   return nullptr;
298 }
299
300 void RemoteProcessMemory::read_variable(const char* name, void* target, size_t size) const
301 {
302   const simgrid::mc::Variable* var = this->find_variable(name);
303   xbt_assert(var, "Variable %s not found", name);
304   xbt_assert(var->address, "No simple location for this variable");
305
306   if (not var->type->full_type) // Try to resolve this type. The needed ObjectInfo was maybe (lazily) loaded recently
307     for (auto const& object_info : this->object_infos)
308       postProcessObjectInformation(this, object_info.get());
309   xbt_assert(var->type->full_type, "Partial type for %s (even after re-resolving types), cannot retrieve its size.",
310              name);
311   xbt_assert((size_t)var->type->full_type->byte_size == size, "Unexpected size for %s (expected %zu, received %zu).",
312              name, size, (size_t)var->type->full_type->byte_size);
313   this->read_bytes(target, size, remote(var->address));
314 }
315
316 std::string RemoteProcessMemory::read_string(RemotePtr<char> address) const
317 {
318   if (not address)
319     return {};
320
321   std::vector<char> res(128);
322   off_t off = 0;
323
324   while (true) {
325     ssize_t c = pread(this->memory_file, res.data() + off, res.size() - off, (off_t)address.address() + off);
326     if (c == -1 && errno == EINTR)
327       continue;
328     xbt_assert(c > 0, "Could not read string from remote process");
329
330     if (memchr(res.data() + off, '\0', c))
331       return res.data();
332
333     off += c;
334     if (off == (off_t)res.size())
335       res.resize(res.size() * 2);
336   }
337 }
338
339 void* RemoteProcessMemory::read_bytes(void* buffer, std::size_t size, RemotePtr<void> address,
340                                       ReadOptions /*options*/) const
341 {
342   xbt_assert(pread_whole(this->memory_file, buffer, size, (size_t)address.address()) != -1,
343              "Read at %p from process %lli failed", (void*)address.address(), (long long)this->pid_);
344   return buffer;
345 }
346
347 /** Write data to a process memory
348  *
349  *  @param buffer   local memory address (source)
350  *  @param len      data size
351  *  @param address  target process memory address (target)
352  */
353 void RemoteProcessMemory::write_bytes(const void* buffer, size_t len, RemotePtr<void> address) const
354 {
355   xbt_assert(pwrite_whole(this->memory_file, buffer, len, (size_t)address.address()) != -1,
356              "Write to process %lli failed", (long long)this->pid_);
357 }
358
359 static void zero_buffer_init(const void** zero_buffer, size_t zero_buffer_size)
360 {
361   int fd = open("/dev/zero", O_RDONLY);
362   xbt_assert(fd >= 0, "Could not open /dev/zero");
363   *zero_buffer = mmap(nullptr, zero_buffer_size, PROT_READ, MAP_SHARED, fd, 0);
364   xbt_assert(*zero_buffer != MAP_FAILED, "Could not map the zero buffer");
365   close(fd);
366 }
367
368 void RemoteProcessMemory::clear_bytes(RemotePtr<void> address, size_t len) const
369 {
370   static constexpr size_t zero_buffer_size = 10 * 4096;
371   static const void* zero_buffer;
372   static std::once_flag zero_buffer_flag;
373
374   std::call_once(zero_buffer_flag, zero_buffer_init, &zero_buffer, zero_buffer_size);
375   while (len) {
376     size_t s = len > zero_buffer_size ? zero_buffer_size : len;
377     this->write_bytes(zero_buffer, s, address);
378     address = remote((char*)address.address() + s);
379     len -= s;
380   }
381 }
382
383 void RemoteProcessMemory::ignore_region(std::uint64_t addr, std::size_t size)
384 {
385   IgnoredRegion region;
386   region.addr = addr;
387   region.size = size;
388
389   auto pos = std::lower_bound(ignored_regions_.begin(), ignored_regions_.end(), region,
390                               [](auto const& reg1, auto const& reg2) {
391                                 return reg1.addr < reg2.addr || (reg1.addr == reg2.addr && reg1.size < reg2.size);
392                               });
393   if (pos == ignored_regions_.end() || pos->addr != addr || pos->size != size)
394     ignored_regions_.insert(pos, region);
395 }
396
397 void RemoteProcessMemory::ignore_heap(IgnoredHeapRegion const& region)
398 {
399   // Binary search the position of insertion:
400   auto pos = std::lower_bound(ignored_heap_.begin(), ignored_heap_.end(), region.address,
401                               [](auto const& reg, auto const* addr) { return reg.address < addr; });
402   if (pos == ignored_heap_.end() || pos->address != region.address) {
403     // Insert it:
404     ignored_heap_.insert(pos, region);
405   }
406 }
407
408 void RemoteProcessMemory::unignore_heap(void* address, size_t size)
409 {
410   // Binary search:
411   auto pos = std::lower_bound(ignored_heap_.begin(), ignored_heap_.end(), address,
412                               [](auto const& reg, auto const* addr) { return reg.address < addr; });
413   if (pos != ignored_heap_.end() && static_cast<char*>(pos->address) <= static_cast<char*>(address) + size)
414     ignored_heap_.erase(pos);
415 }
416
417 void RemoteProcessMemory::ignore_local_variable(const char* var_name, const char* frame_name) const
418 {
419   if (frame_name != nullptr && strcmp(frame_name, "*") == 0)
420     frame_name = nullptr;
421   for (std::shared_ptr<simgrid::mc::ObjectInformation> const& info : this->object_infos)
422     info->remove_local_variable(var_name, frame_name);
423 }
424
425 void RemoteProcessMemory::dump_stack() const
426 {
427   unw_addr_space_t as = unw_create_addr_space(&_UPT_accessors, BYTE_ORDER);
428   if (as == nullptr) {
429     XBT_ERROR("Could not initialize ptrace address space");
430     return;
431   }
432
433   void* context = _UPT_create(this->pid_);
434   if (context == nullptr) {
435     unw_destroy_addr_space(as);
436     XBT_ERROR("Could not initialize ptrace context");
437     return;
438   }
439
440   unw_cursor_t cursor;
441   if (unw_init_remote(&cursor, as, context) != 0) {
442     _UPT_destroy(context);
443     unw_destroy_addr_space(as);
444     XBT_ERROR("Could not initialize ptrace cursor");
445     return;
446   }
447
448   simgrid::mc::dumpStack(stderr, &cursor);
449
450   _UPT_destroy(context);
451   unw_destroy_addr_space(as);
452 }
453 } // namespace simgrid::mc