mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 08:47:44 +00:00
Implement loading of linked ELF executables.
This took me a couple hours. :^) The ELF loading code now allocates a single region for the entire file and creates virtual memory mappings for the sections as needed. Very nice!
This commit is contained in:
parent
99ee6acd69
commit
9a71c7759a
16 changed files with 258 additions and 57 deletions
|
@ -55,8 +55,7 @@ ARCH_FLAGS =
|
|||
STANDARD_FLAGS = -std=c++17 -nostdinc++ -nostdlib
|
||||
KERNEL_FLAGS = -ffreestanding -fno-stack-protector -fno-ident
|
||||
WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings
|
||||
FLAVOR_FLAGS = -mregparm=3 -march=i386 -m32 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -fmerge-all-constants -fno-unroll-loops -falign-functions=1 -falign-jumps=1 -falign-loops=1 -fno-pie -fno-pic
|
||||
#FLAVOR_FLAGS = -mregparm=3 -march=i386 -m32 -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections
|
||||
FLAVOR_FLAGS = -mregparm=3 -march=i386 -m32 -fno-exceptions -fno-rtti -fmerge-all-constants -fno-unroll-loops -fno-pie -fno-pic
|
||||
OPTIMIZATION_FLAGS = -Os -fno-asynchronous-unwind-tables
|
||||
INCLUDE_FLAGS = -I.. -I.
|
||||
|
||||
|
@ -65,11 +64,10 @@ DEFINES = -DSERENITY -DSANITIZE_PTRS
|
|||
CXXFLAGS = $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(KERNEL_FLAGS) $(FLAVOR_FLAGS) $(ARCH_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES)
|
||||
#CXX = /usr/local/gcc-4.8.1-for-linux64/bin/x86_64-pc-linux-g++
|
||||
#LD = /usr/local/gcc-4.8.1-for-linux64/bin/x86_64-pc-linux-ld
|
||||
CXX = g++
|
||||
CXX = g++-8
|
||||
LD = ld
|
||||
LDFLAGS = -T linker.ld --strip-debug -melf_i386 --gc-sections --build-id=none -z norelro -z now
|
||||
|
||||
|
||||
all: $(KERNEL) $(IMAGE) kernel.map
|
||||
|
||||
kernel.map: kernel
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
static MemoryManager* s_the;
|
||||
|
||||
MemoryManager& MemoryManager::the()
|
||||
MemoryManager& MM
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ void MemoryManager::initializePaging()
|
|||
|
||||
identityMap(LinearAddress(4096), 4 * MB);
|
||||
|
||||
// Put pages between 4MB and 16MB in the page freelist.
|
||||
for (size_t i = (4 * MB) + 1024; i < (16 * MB); i += PAGE_SIZE) {
|
||||
// Put pages between 4MB and 8MB in the page freelist.
|
||||
for (size_t i = (4 * MB) + PAGE_SIZE; i < (8 * MB); i += PAGE_SIZE) {
|
||||
m_freePages.append(PhysicalAddress(i));
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,14 @@ void MemoryManager::initializePaging()
|
|||
);
|
||||
}
|
||||
|
||||
void* MemoryManager::allocatePageTable()
|
||||
{
|
||||
auto ppages = allocatePhysicalPages(1);
|
||||
dword address = ppages[0].get();
|
||||
identityMap(LinearAddress(address), 4096);
|
||||
return (void*)address;
|
||||
}
|
||||
|
||||
auto MemoryManager::ensurePTE(LinearAddress linearAddress) -> PageTableEntry
|
||||
{
|
||||
ASSERT_INTERRUPTS_DISABLED();
|
||||
|
@ -76,8 +84,13 @@ auto MemoryManager::ensurePTE(LinearAddress linearAddress) -> PageTableEntry
|
|||
pde.setPresent(true);
|
||||
pde.setWritable(true);
|
||||
} else {
|
||||
// FIXME: We need an allocator!
|
||||
ASSERT_NOT_REACHED();
|
||||
auto* pageTable = allocatePageTable();
|
||||
kprintf("allocated page table %u (for laddr=%p) at %p\n", pageDirectoryIndex, linearAddress.get(), pageTable);
|
||||
memset(pageTable, 0, 4096);
|
||||
pde.setPageTableBase((dword)pageTable);
|
||||
pde.setUserAllowed(true);
|
||||
pde.setPresent(true);
|
||||
pde.setWritable(true);
|
||||
}
|
||||
}
|
||||
return PageTableEntry(&pde.pageTableBase()[pageTableIndex]);
|
||||
|
@ -190,7 +203,27 @@ bool MemoryManager::unmapRegion(Task& task, Task::Region& region)
|
|||
pte.setWritable(false);
|
||||
pte.setUserAllowed(false);
|
||||
flushTLB(laddr);
|
||||
// kprintf("MM: >> Unmapped L%x => P%x <<\n", laddr, zone.m_pages[i].get());
|
||||
//kprintf("MM: >> Unmapped L%x => P%x <<\n", laddr, zone.m_pages[i].get());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryManager::unmapSubregion(Task& task, Task::Subregion& subregion)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
auto& region = *subregion.region;
|
||||
auto& zone = *region.zone;
|
||||
size_t numPages = subregion.size / 4096;
|
||||
ASSERT(numPages);
|
||||
for (size_t i = 0; i < numPages; ++i) {
|
||||
auto laddr = subregion.linearAddress.offset(i * PAGE_SIZE);
|
||||
auto pte = ensurePTE(laddr);
|
||||
pte.setPhysicalPageBase(0);
|
||||
pte.setPresent(false);
|
||||
pte.setWritable(false);
|
||||
pte.setUserAllowed(false);
|
||||
flushTLB(laddr);
|
||||
//kprintf("MM: >> Unmapped subregion %s L%x => P%x <<\n", subregion.name.characters(), laddr, zone.m_pages[i].get());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -202,6 +235,31 @@ bool MemoryManager::unmapRegionsForTask(Task& task)
|
|||
if (!unmapRegion(task, *region))
|
||||
return false;
|
||||
}
|
||||
for (auto& subregion : task.m_subregions) {
|
||||
if (!unmapSubregion(task, *subregion))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryManager::mapSubregion(Task& task, Task::Subregion& subregion)
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
auto& region = *subregion.region;
|
||||
auto& zone = *region.zone;
|
||||
size_t firstPage = subregion.offset / 4096;
|
||||
size_t numPages = subregion.size / 4096;
|
||||
ASSERT(numPages);
|
||||
for (size_t i = 0; i < numPages; ++i) {
|
||||
auto laddr = subregion.linearAddress.offset(i * PAGE_SIZE);
|
||||
auto pte = ensurePTE(laddr);
|
||||
pte.setPhysicalPageBase(zone.m_pages[firstPage + i].get());
|
||||
pte.setPresent(true);
|
||||
pte.setWritable(true);
|
||||
pte.setUserAllowed(!task.isRing0());
|
||||
flushTLB(laddr);
|
||||
//kprintf("MM: >> Mapped subregion %s L%x => P%x (%u into region)<<\n", subregion.name.characters(), laddr, zone.m_pages[firstPage + i].get(), subregion.offset);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -229,6 +287,10 @@ bool MemoryManager::mapRegionsForTask(Task& task)
|
|||
if (!mapRegion(task, *region))
|
||||
return false;
|
||||
}
|
||||
for (auto& subregion : task.m_subregions) {
|
||||
if (!mapSubregion(task, *subregion))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -243,7 +305,7 @@ bool copyToZone(Zone& zone, const void* data, size_t size)
|
|||
auto* dataptr = (const byte*)data;
|
||||
size_t remaining = size;
|
||||
for (size_t i = 0; i < zone.m_pages.size(); ++i) {
|
||||
byte* dest = MemoryManager::the().quickMapOnePage(zone.m_pages[i]);
|
||||
byte* dest = MM.quickMapOnePage(zone.m_pages[i]);
|
||||
kprintf("memcpy(%p, %p, %u)\n", dest, dataptr, min(PAGE_SIZE, remaining));
|
||||
memcpy(dest, dataptr, min(PAGE_SIZE, remaining));
|
||||
dataptr += PAGE_SIZE;
|
||||
|
|
|
@ -35,6 +35,8 @@ private:
|
|||
|
||||
bool copyToZone(Zone&, const void* data, size_t);
|
||||
|
||||
#define MM MemoryManager::the()
|
||||
|
||||
class MemoryManager {
|
||||
public:
|
||||
static MemoryManager& the() PURE;
|
||||
|
@ -50,6 +52,11 @@ public:
|
|||
// HACK: don't use this jeez :(
|
||||
byte* quickMapOnePage(PhysicalAddress);
|
||||
|
||||
bool mapSubregion(Task&, Task::Subregion&);
|
||||
bool unmapSubregion(Task&, Task::Subregion&);
|
||||
bool mapSubregionsForTask(Task&);
|
||||
bool unmapSubregionsForTask(Task&);
|
||||
|
||||
bool mapRegion(Task&, Task::Region&);
|
||||
bool unmapRegion(Task&, Task::Region&);
|
||||
bool mapRegionsForTask(Task&);
|
||||
|
@ -63,6 +70,8 @@ private:
|
|||
void flushEntireTLB();
|
||||
void flushTLB(LinearAddress);
|
||||
|
||||
void* allocatePageTable();
|
||||
|
||||
void protectMap(LinearAddress, size_t length);
|
||||
void identityMap(LinearAddress, size_t length);
|
||||
|
||||
|
|
|
@ -49,8 +49,8 @@ ByteBuffer procfs$pid_stack(Task& task)
|
|||
{
|
||||
InterruptDisabler disabler;
|
||||
if (current != &task) {
|
||||
MemoryManager::the().unmapRegionsForTask(*current);
|
||||
MemoryManager::the().mapRegionsForTask(task);
|
||||
MM.unmapRegionsForTask(*current);
|
||||
MM.mapRegionsForTask(task);
|
||||
}
|
||||
struct RecognizedSymbol {
|
||||
dword address;
|
||||
|
@ -78,8 +78,8 @@ ByteBuffer procfs$pid_stack(Task& task)
|
|||
}
|
||||
buffer.trim(bufptr - (char*)buffer.pointer());
|
||||
if (current != &task) {
|
||||
MemoryManager::the().unmapRegionsForTask(task);
|
||||
MemoryManager::the().mapRegionsForTask(*current);
|
||||
MM.unmapRegionsForTask(task);
|
||||
MM.mapRegionsForTask(*current);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "i8253.h"
|
||||
#include "RTC.h"
|
||||
#include "ProcFileSystem.h"
|
||||
#include <AK/StdLib.h>
|
||||
|
||||
//#define DEBUG_IO
|
||||
//#define TASK_DEBUG
|
||||
|
@ -132,9 +133,9 @@ Task::Region* Task::allocateRegion(size_t size, String&& name)
|
|||
{
|
||||
// FIXME: This needs sanity checks. What if this overlaps existing regions?
|
||||
|
||||
auto zone = MemoryManager::the().createZone(size);
|
||||
auto zone = MM.createZone(size);
|
||||
ASSERT(zone);
|
||||
m_regions.append(make<Region>(m_nextRegion, size, move(zone), move(name)));
|
||||
m_regions.append(adopt(*new Region(m_nextRegion, size, move(zone), move(name))));
|
||||
m_nextRegion = m_nextRegion.offset(size).offset(16384);
|
||||
return m_regions.last().ptr();
|
||||
}
|
||||
|
@ -144,7 +145,7 @@ bool Task::deallocateRegion(Region& region)
|
|||
for (size_t i = 0; i < m_regions.size(); ++i) {
|
||||
if (m_regions[i].ptr() == ®ion) {
|
||||
// FIXME: This seems racy.
|
||||
MemoryManager::the().unmapRegion(*this, region);
|
||||
MM.unmapRegion(*this, region);
|
||||
m_regions.remove(i);
|
||||
return true;
|
||||
}
|
||||
|
@ -168,7 +169,7 @@ void* Task::sys$mmap(void* addr, size_t size)
|
|||
auto* region = allocateRegion(size, "mmap");
|
||||
if (!region)
|
||||
return (void*)-1;
|
||||
MemoryManager::the().mapRegion(*this, *region);
|
||||
MM.mapRegion(*this, *region);
|
||||
return (void*)region->linearAddress.get();
|
||||
}
|
||||
|
||||
|
@ -250,39 +251,55 @@ Task* Task::createUserTask(const String& path, uid_t uid, gid_t gid, pid_t paren
|
|||
t->m_arguments = move(taskArguments);
|
||||
|
||||
ExecSpace space;
|
||||
Region* region = nullptr;
|
||||
space.hookableAlloc = [&] (const String& name, size_t size) {
|
||||
if (!size)
|
||||
return (void*)nullptr;
|
||||
size = ((size / 4096) + 1) * 4096;
|
||||
Region* region = t->allocateRegion(size, String(name));
|
||||
region = t->allocateRegion(size, String(name));
|
||||
ASSERT(region);
|
||||
MemoryManager::the().mapRegion(*t, *region);
|
||||
MM.mapRegion(*t, *region);
|
||||
return (void*)region->linearAddress.asPtr();
|
||||
};
|
||||
bool success = space.loadELF(move(elfData));
|
||||
if (!success) {
|
||||
// FIXME: This is ugly. If we need to do this, it should be at a different level.
|
||||
MemoryManager::the().unmapRegionsForTask(*t);
|
||||
MemoryManager::the().mapRegionsForTask(*current);
|
||||
MM.unmapRegionsForTask(*t);
|
||||
MM.mapRegionsForTask(*current);
|
||||
delete t;
|
||||
kprintf("Failure loading ELF %s\n", path.characters());
|
||||
error = -ENOEXEC;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
space.forEachArea([&] (const String& name, dword offset, size_t size, LinearAddress laddr) {
|
||||
if (laddr.isNull())
|
||||
return;
|
||||
dword roundedOffset = offset & 0xfffff000;
|
||||
size_t roundedSize = 4096 * ceilDiv((offset - roundedOffset) + size, 4096u);
|
||||
LinearAddress roundedLaddr = laddr;
|
||||
roundedLaddr.mask(0xfffff000);
|
||||
t->m_subregions.append(make<Subregion>(*region, roundedOffset, roundedSize, roundedLaddr, String(name)));
|
||||
#ifdef SUBREGION_DEBUG
|
||||
kprintf(" req subregion %s (offset: %u, size: %u) @ %p\n", name.characters(), offset, size, laddr.get());
|
||||
kprintf("actual subregion %s (offset: %u, size: %u) @ %p\n", name.characters(), roundedOffset, roundedSize, roundedLaddr.get());
|
||||
#endif
|
||||
MM.mapSubregion(*t, *t->m_subregions.last());
|
||||
});
|
||||
|
||||
t->m_tss.eip = (dword)space.symbolPtr("_start");
|
||||
if (!t->m_tss.eip) {
|
||||
// FIXME: This is ugly. If we need to do this, it should be at a different level.
|
||||
MemoryManager::the().unmapRegionsForTask(*t);
|
||||
MemoryManager::the().mapRegionsForTask(*current);
|
||||
MM.unmapRegionsForTask(*t);
|
||||
MM.mapRegionsForTask(*current);
|
||||
delete t;
|
||||
error = -ENOEXEC;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// FIXME: This is ugly. If we need to do this, it should be at a different level.
|
||||
MemoryManager::the().unmapRegionsForTask(*t);
|
||||
MemoryManager::the().mapRegionsForTask(*current);
|
||||
MM.unmapRegionsForTask(*t);
|
||||
MM.mapRegionsForTask(*current);
|
||||
|
||||
s_tasks->prepend(t);
|
||||
system.nprocess++;
|
||||
|
@ -299,7 +316,7 @@ int Task::sys$get_arguments(int* argc, char*** argv)
|
|||
auto* region = allocateRegion(4096, "argv");
|
||||
if (!region)
|
||||
return -ENOMEM;
|
||||
MemoryManager::the().mapRegion(*this, *region);
|
||||
MM.mapRegion(*this, *region);
|
||||
char* argpage = (char*)region->linearAddress.get();
|
||||
*argc = m_arguments.size();
|
||||
*argv = (char**)argpage;
|
||||
|
@ -380,7 +397,7 @@ Task::Task(String&& name, uid_t uid, gid_t gid, pid_t parentPID, RingLevel ring)
|
|||
m_tss.ss = ss;
|
||||
m_tss.cs = cs;
|
||||
|
||||
m_tss.cr3 = MemoryManager::the().pageDirectoryBase().get();
|
||||
m_tss.cr3 = MM.pageDirectoryBase().get();
|
||||
|
||||
if (isRing0()) {
|
||||
// FIXME: This memory is leaked.
|
||||
|
@ -435,6 +452,18 @@ void Task::dumpRegions()
|
|||
region->size,
|
||||
region->name.characters());
|
||||
}
|
||||
|
||||
kprintf("Task %s(%u) subregions:\n", name().characters(), pid());
|
||||
kprintf("REGION OFFSET BEGIN END SIZE NAME\n");
|
||||
for (auto& subregion : m_subregions) {
|
||||
kprintf("%x %x %x -- %x %x %s\n",
|
||||
subregion->region->linearAddress.get(),
|
||||
subregion->offset,
|
||||
subregion->linearAddress.get(),
|
||||
subregion->linearAddress.offset(subregion->size - 1).get(),
|
||||
subregion->size,
|
||||
subregion->name.characters());
|
||||
}
|
||||
}
|
||||
|
||||
void Task::sys$exit(int status)
|
||||
|
@ -446,7 +475,7 @@ void Task::sys$exit(int status)
|
|||
|
||||
setState(Exiting);
|
||||
|
||||
MemoryManager::the().unmapRegionsForTask(*this);
|
||||
MM.unmapRegionsForTask(*this);
|
||||
|
||||
s_tasks->remove(this);
|
||||
|
||||
|
@ -474,7 +503,7 @@ void Task::taskDidCrash(Task* crashedTask)
|
|||
|
||||
s_tasks->remove(crashedTask);
|
||||
|
||||
MemoryManager::the().unmapRegionsForTask(*crashedTask);
|
||||
MM.unmapRegionsForTask(*crashedTask);
|
||||
|
||||
if (!scheduleNewTask()) {
|
||||
kprintf("Task::taskDidCrash: Failed to schedule a new task :(\n");
|
||||
|
@ -630,11 +659,11 @@ static bool contextSwitch(Task* t)
|
|||
if (current->state() == Task::Running)
|
||||
current->setState(Task::Runnable);
|
||||
|
||||
bool success = MemoryManager::the().unmapRegionsForTask(*current);
|
||||
bool success = MM.unmapRegionsForTask(*current);
|
||||
ASSERT(success);
|
||||
}
|
||||
|
||||
bool success = MemoryManager::the().mapRegionsForTask(*t);
|
||||
bool success = MM.mapRegionsForTask(*t);
|
||||
ASSERT(success);
|
||||
|
||||
current = t;
|
||||
|
@ -911,6 +940,20 @@ Task::Region::~Region()
|
|||
{
|
||||
}
|
||||
|
||||
Task::Subregion::Subregion(Region& r, dword o, size_t s, LinearAddress l, String&& n)\
|
||||
: region(r)
|
||||
, offset(o)
|
||||
, size(s)
|
||||
, linearAddress(l)
|
||||
, name(move(n))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Task::Subregion::~Subregion()
|
||||
{
|
||||
}
|
||||
|
||||
bool Task::isValidAddressForKernel(LinearAddress laddr) const
|
||||
{
|
||||
InterruptDisabler disabler;
|
||||
|
@ -928,5 +971,9 @@ bool Task::isValidAddressForUser(LinearAddress laddr) const
|
|||
if (laddr >= region->linearAddress && laddr < region->linearAddress.offset(region->size))
|
||||
return true;
|
||||
}
|
||||
for (auto& subregion: m_subregions) {
|
||||
if (laddr >= subregion->linearAddress && laddr < subregion->linearAddress.offset(subregion->size))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ public:
|
|||
static void taskDidCrash(Task*);
|
||||
|
||||
size_t regionCount() const { return m_regions.size(); }
|
||||
const Vector<OwnPtr<Region>>& regions() const { return m_regions; }
|
||||
const Vector<RetainPtr<Region>>& regions() const { return m_regions; }
|
||||
void dumpRegions();
|
||||
|
||||
void didSchedule() { ++m_timesScheduled; }
|
||||
|
@ -166,7 +166,7 @@ private:
|
|||
|
||||
RetainPtr<VirtualFileSystem::Node> m_cwd;
|
||||
|
||||
struct Region {
|
||||
struct Region : public Retainable<Region> {
|
||||
Region(LinearAddress, size_t, RetainPtr<Zone>&&, String&&);
|
||||
~Region();
|
||||
LinearAddress linearAddress;
|
||||
|
@ -174,12 +174,26 @@ private:
|
|||
RetainPtr<Zone> zone;
|
||||
String name;
|
||||
};
|
||||
|
||||
struct Subregion {
|
||||
Subregion(Region&, dword offset, size_t, LinearAddress, String&& name);
|
||||
~Subregion();
|
||||
|
||||
RetainPtr<Region> region;
|
||||
dword offset;
|
||||
size_t size { 0 };
|
||||
LinearAddress linearAddress;
|
||||
String name;
|
||||
};
|
||||
|
||||
Region* allocateRegion(size_t, String&& name);
|
||||
Region* allocateRegion(size_t, String&& name, LinearAddress);
|
||||
bool deallocateRegion(Region& region);
|
||||
|
||||
Region* regionFromRange(LinearAddress, size_t);
|
||||
|
||||
Vector<OwnPtr<Region>> m_regions;
|
||||
Vector<RetainPtr<Region>> m_regions;
|
||||
Vector<OwnPtr<Subregion>> m_subregions;
|
||||
|
||||
// FIXME: Implement some kind of ASLR?
|
||||
LinearAddress m_nextRegion;
|
||||
|
|
|
@ -218,10 +218,22 @@ void exception_14_handler()
|
|||
kprintf("eax=%x ebx=%x ecx=%x edx=%x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
|
||||
kprintf("ebp=%x esp=%x esi=%x edi=%x\n", regs.ebp, esp, regs.esi, regs.edi);
|
||||
|
||||
byte* codeptr = (byte*)regs.eip;
|
||||
kprintf("code: %b %b %b %b %b %b %b %b\n",
|
||||
codeptr[0],
|
||||
codeptr[1],
|
||||
codeptr[2],
|
||||
codeptr[3],
|
||||
codeptr[4],
|
||||
codeptr[5],
|
||||
codeptr[6],
|
||||
codeptr[7]
|
||||
);
|
||||
|
||||
if (current->isRing0())
|
||||
HANG;
|
||||
|
||||
auto response = MemoryManager::the().handlePageFault(PageFault(exception_code, LinearAddress(faultAddress)));
|
||||
auto response = MM.handlePageFault(PageFault(exception_code, LinearAddress(faultAddress)));
|
||||
|
||||
if (response == PageFaultResponse::ShouldCrash) {
|
||||
kprintf("Crashing after unresolved page fault\n");
|
||||
|
|
|
@ -80,6 +80,8 @@ public:
|
|||
LinearAddress() { }
|
||||
explicit LinearAddress(dword address) : m_address(address) { }
|
||||
|
||||
bool isNull() const { return m_address == 0; }
|
||||
|
||||
LinearAddress offset(dword o) const { return LinearAddress(m_address + o); }
|
||||
dword get() const { return m_address; }
|
||||
void set(dword address) { m_address = address; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue