Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb] IRMemoryMap zero address mapping fix #99045

Closed

Conversation

dlav-sc
Copy link
Contributor

@dlav-sc dlav-sc commented Jul 16, 2024

Some tests check error receiving after nullptr dereference during lldb exression. On RISCV targets IRMemoryMap could map zero address during allocations taking into account that host process doesn't dereference nullptr during normal workflow making dereference of zero address valid. This patch adds a check to avoid cases like this.

Fixed tests for RISCV target:

TestEnumTypes.EnumTypesTestCase
TestAnonymous.AnonymousTestCase

Some tests check error receiving after nullptr dereference during lldb
exression. On RISCV targets IRMemoryMap could map zero address during
allocations taking into account that host process doesn't dereference
nullptr during normal workflow making dereference of zero address valid.
This patch adds a check to avoid cases like this.

Fixed tests for RISCV target:

TestEnumTypes.EnumTypesTestCase
TestAnonymous.AnonymousTestCase
@dlav-sc dlav-sc requested a review from JDevlieghere as a code owner July 16, 2024 13:42
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be
notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write
permissions for the repository. In which case you can instead tag reviewers by
name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review
by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate
is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added the lldb label Jul 16, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 16, 2024

@llvm/pr-subscribers-lldb

Author: None (dlav-sc)

Changes

Some tests check error receiving after nullptr dereference during lldb exression. On RISCV targets IRMemoryMap could map zero address during allocations taking into account that host process doesn't dereference nullptr during normal workflow making dereference of zero address valid. This patch adds a check to avoid cases like this.

Fixed tests for RISCV target:

TestEnumTypes.EnumTypesTestCase
TestAnonymous.AnonymousTestCase


Full diff: https://github.com/llvm/llvm-project/pull/99045.diff

1 Files Affected:

  • (modified) lldb/source/Expression/IRMemoryMap.cpp (+4)
diff --git a/lldb/source/Expression/IRMemoryMap.cpp b/lldb/source/Expression/IRMemoryMap.cpp
index de631370bb048..a15645e2b5b48 100644
--- a/lldb/source/Expression/IRMemoryMap.cpp
+++ b/lldb/source/Expression/IRMemoryMap.cpp
@@ -126,6 +126,10 @@ lldb::addr_t IRMemoryMap::FindSpace(size_t size) {
           } else {
             ret = region_info.GetRange().GetRangeEnd();
           }
+        } else if (ret == 0x0) {
+          // Get other region if zero address. Should't map zero address to
+          // get an error in cases when user tries to dereference nullptr
+          ret = region_info.GetRange().GetRangeEnd();
         } else if (ret + size < region_info.GetRange().GetRangeEnd()) {
           return ret;
         } else {

@Michael137 Michael137 requested a review from jasonmolenda July 16, 2024 13:54
@DavidSpickett
Copy link
Collaborator

Is this something specific to risc-v or simply uncovered by testing against a certain risc-v target? Just wondering why we haven't had to do this before now.

@jasonmolenda
Copy link
Collaborator

jasonmolenda commented Jul 16, 2024

When we're connected to a stub that can allocate memory in the target, none of this code is executed. So lldb-server/debugserver will not hit this.

We send qMemoryRegionInfo packets to look for a block of memory that is unallocated, or has permissions no-read+no-write+no-execute. The memory region scan starts at address 0 and moves up by the size of each memory region, looking for size bytes between allocated memory regions, and returns that address if it is found.

So I'm guessing we have (1) a target that cannot allocate memory, (2) a target that supported qMemoryRegionInfo, a non-accessible block of memory at address 0? (did I read this right?) and in that case, we end up picking address 0 as our host-side virtual address+memory buffer for expression data storage.

Can you show what your qMemoryRegionInfo results are for address 0 on this target, so I can confirm I didn't misread/misunderstand the situation here?

@jasonmolenda
Copy link
Collaborator

To make sure I'm clear: I don't have a problem with the basic idea of the change, although we could comment what is going on more clearly, and I'm curious about that qMemoryRegionInfo packet. But it looks like you're connecting to a device which can't allocate memory through gdb remote serial protocol (common for jtag/vm type stubs), but can report memory region info (much less common, it's an lldb extension I believe), and the memory region at address 0 is reported as being inaccessible (reasonable). So the IRMemoryMap is trying to use address 0 and that overlaps with actual addresses seen in the process, leading to confusion.

Most similar environments, that can't allocate memory, don't support qMemoryRegionInfo so we pick one of our bogus addresses ranges (in higher memory) to get a better chance of avoiding actual program addresses.

@dlav-sc
Copy link
Contributor Author

dlav-sc commented Jul 18, 2024

I connect to a riscv (rv64gc) machine, nothing special.

Lldb can just allocate memory on remote target only if the target supports executing JIT-compiled code (if (process_is_alive && process_sp->CanJIT()) on the line 68), otherwise lldb falls through and starts finding suitable memory region for a mapping.

The problem is that riscv targets currently don't support JIT-compiled code execution, but can only execute simple lldb expressions interpreting its IR, like for example

expr <variable>

or

expr *(type_z*)pz

If in the case above pz == nullptr and lldb have mapped zero address, then instead of the error we will get a valid result:

output: (type_z) $0 = {
  y = (dummy = 0)
}

I take this example from TestAnonymous.py.

Logs you have asked for:

Without patch:

lldb             <  23> send packet: $qMemoryRegionInfo:0#44                                                                                                                                                                                                                       
lldb             <  28> read packet: $start:0;size:2aaaaaa000;#0b                                                                                                                                                                                                                  
lldb             IRMemoryMap::Malloc (23, 0x8, 0x3, eAllocationPolicyHostOnly) -> 0x0                                                                                                                                                                                              
lldb             <  26> send packet: $qMemoryRegionInfo:1000#d5                                                                                                                                                                                                                    
lldb             <  31> read packet: $start:1000;size:2aaaaa9000;#74                                                                                                                                                                                                               
lldb             IRMemoryMap::Malloc (524295, 0x8, 0x3, eAllocationPolicyHostOnly) -> 0x1000

With patch:

lldb             <  23> send packet: $qMemoryRegionInfo:0#44                                                                                                                                                                                                                       
lldb             <  28> read packet: $start:0;size:2aaaaaa000;#0b                                                                                                                                                                                                                  
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaaaa000#1c                                                                                                                                                                                                              
lldb             <  81> read packet: $start:2aaaaaa000;size:1000;permissions:rx;flags:;name:2f726f6f742f612e6f7574;#fc                                                                                                                                                             
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaaab000#1d                                                                                                                                                                                                              
lldb             <  80> read packet: $start:2aaaaab000;size:1000;permissions:r;flags:;name:2f726f6f742f612e6f7574;#85                                                                                                                                                              
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaaac000#1e                                                                                                                                                                                                              
lldb             <  81> read packet: $start:2aaaaac000;size:1000;permissions:rw;flags:;name:2f726f6f742f612e6f7574;#fd                                                                                                                                                             
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaaad000#1f                                                                                                                                                                                                              
lldb             <  72> read packet: $start:2aaaaad000;size:21000;permissions:rw;flags:;name:5b686561705d;#5a                                                                                                                                                                      
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaace000#22                                                                                                                                                                                                              
lldb             <  37> read packet: $start:2aaaace000;size:154d0a2000;#32                                                                                                                                                                                                         
lldb             IRMemoryMap::Malloc (23, 0x8, 0x3, eAllocationPolicyHostOnly) -> 0x2aaaace000                                                                                                                                                                                     
lldb             <  32> send packet: $qMemoryRegionInfo:2aaaacf000#23                                                                                                                                                                                                              
lldb             <  37> read packet: $start:2aaaacf000;size:154d0a1000;#32                                                                                                                                                                                                         
lldb             IRMemoryMap::Malloc (524295, 0x8, 0x3, eAllocationPolicyHostOnly) -> 0x2aaaacf000

Actually, I have solved the problem with this patch: #99336. It adds the ability to make function calls inside lldb expressions, including jitted code execution support for riscv targets.

I thought maybe this patch may be usefull for other architectures that can't execute JIT code.

@jasonmolenda
Copy link
Collaborator

I'd describe this patch as handling the case where a remote stub cannot jit memory and has a qMemoryRegion info packet which does not specify permissions. The patch you reference makes jitted code expressions work, but I think IRMemoryMap::FindSpec only needs to find space in the inferior virtual address space -- allocate memory -- and that was already working, wasn't it? Also, your before & after packets show that you've got permissions returned for your qMemoryRegionInfo responses now, which you don't before -- you won't hit this failure because of that change.

One thing I'd say is that this patch is looking for "is this a region with read, write, or execute permissions", so it can skip that. It does this with,

        if (region_info.GetReadable() != MemoryRegionInfo::OptionalBool::eNo ||
            region_info.GetWritable() != MemoryRegionInfo::OptionalBool::eNo ||
            region_info.GetExecutable() !=
                MemoryRegionInfo::OptionalBool::eNo) {
            ret = region_info.GetRange().GetRangeEnd();
 /// keep iterating

but MemoryRegionInfo::OptionalBool can be eDontKnow which it is here. I think this conditional would be more clearly expressed as

       bool all_permissions_unknown = region_info.GetReadable() == MemoryRegionInfo::OptionalBool::eDontKnow &&
                                                                region_info.GetWritable() == MemoryRegionInfo::OptionalBool::eDontKnow &&
                                                                 region_info.GetExecutable() == MemoryRegionInfo::OptionalBool::eDontKnow;
       bool any_permissions_yes = region_info.GetReadable() == MemoryRegionInfo::OptionalBool::eYes
                                                        region_info.GetWritable() == MemoryRegionInfo::OptionalBool::eYes
                                                         region_info.GetExecutable() ==  MemoryRegionInfo::OptionalBool::eYes;
        if (all_permissions_unknown || any_permissions_yes)
            ret = region_info.GetRange().GetRangeEnd();
 /// keep iterating

or more simply, change the while (true) loop to first check if all permissions are No in a region,

        if (region_info.GetReadable() != MemoryRegionInfo::OptionalBool::eNo &&
            region_info.GetWritable() != MemoryRegionInfo::OptionalBool::eNo &&
            region_info.GetExecutable() !=
                MemoryRegionInfo::OptionalBool::eNo) {
           if (ret + size < region_info.GetRange().GetRangeEnd())
            return ret;
           else 
             ret = region_info.GetRange().GetRangeEnd();
             // keep searching

@jasonmolenda
Copy link
Collaborator

I think we should change these checks to look for an explicitly inaccessible memory region, like

       if (region_info.GetReadable() != MemoryRegionInfo::OptionalBool::eNo &&
            region_info.GetWritable() != MemoryRegionInfo::OptionalBool::eNo &&
            region_info.GetExecutable() !=
                MemoryRegionInfo::OptionalBool::eNo) {
           // This region is inaccessible, if it is large enough, use this address.
           if (ret + size < region_info.GetRange().GetRangeEnd())
            return ret;
           else 
             ret = region_info.GetRange().GetRangeEnd();
             // keep searching

and I also do think there is value in adding a special case for address 0. Even if we have an inaddressable memory block at address 0 which should be eligible for shadowing with lldb's host memory, using that is a poor choice because people crash at address 0 all the time and we don't want references to that address finding the IRMemoryMap host side memory values.

        } else if (ret == 0) {
           // Don't put our host-side memory block at virtual address 0 even if it
           // seems eligible, we don't want to confuse things when a program 
           // crashes accessing that address, a common way of crashing.
           ret = region_info.GetRange().GetRangeEnd();

@jasonmolenda
Copy link
Collaborator

jasonmolenda commented Jul 20, 2024

I haven't tested it (or even tried to compile it, lol) but I think this loop might be expressable as simply

    MemoryRegionInfo region_info;
    while (process_sp->GetMemoryRegionInfo(ret, region_info) == err.Success() &&
        region_info.GetRange().GetRangeEnd() - 1 < end_of_memory) {
      
      // Don't ever choose a memory region starting at address 0,
      // it will conflict with programs that deference null pointers.
      if (ret == 0) {
        ret = region_info.GetRange().GetRangeEnd();
        continue;
      }

      // A memory region that is inaccessible, and large enough, is a good
      // choice.  
      if (region_info.GetReadable() != MemoryRegionInfo::OptionalBool::eNo && 
          region_info.GetWritable() != MemoryRegionInfo::OptionalBool::eNo && 
          region_info.GetExecutable() != MemoryRegionInfo::OptionalBool::eNo) {
         if (ret + size < region_info.GetRange().GetRangeEnd()) {
          return ret;
         }
      }
      
      // Get the next region.
      ret = region_info.GetRange().GetRangeEnd();
    }

I dropped two behaviors here - one is that it would emit a unique assert if qMemoryRegionInfo worked once, but failed for a different address. The second is that I think the old code would try to combine consecutive memory regions to make one block large enough to satisfy the size requirement (not sure it was doing this correctly).

@jasonmolenda
Copy link
Collaborator

Ah, but I see my misunderstanding as I look at what debugserver returns. It uses "no permissions" to indicate a memory range that is unmapped --

 <  23> send packet: $qMemoryRegionInfo:0#44
 <  27> read packet: $start:0;size:100000000;#00
 <  31> send packet: $qMemoryRegionInfo:100000000#c5
 <  67> read packet: $start:100000000;size:4000;permissions:rx;dirty-pages:100000000;#00
 <  31> send packet: $qMemoryRegionInfo:100004000#c9
 <  57> read packet: $start:100004000;size:4000;permissions:r;dirty-pages:;#00
 <  31> send packet: $qMemoryRegionInfo:100008000#cd
 <  32> read packet: $start:100008000;size:404000;#00
 <  31> send packet: $qMemoryRegionInfo:10040c000#fc
 < 198> read packet: $start:10040c000;size:40000;permissions:rw;dirty-pages:10040c000,100410000,100414000,100418000,10041c000,100420000,100424000,100428000,10042c000,100430000,100434000,100438000,10043c000,100440000;#00

0x100008000-0x000000010040c000 is a free address range in this example, and what this loop should choose if it had to find an unused memory range. That conflicts with the behavior that this PR started with originally, where no permissions were returned for any memory regions --- which looks like the way debugserver reports a free memory region.

(The low 4 GB segment at the start of my output above is the PAGEZERO empty segment of virtual address space on 64-bit darwin processes)

@jasonmolenda
Copy link
Collaborator

I put up a PR that avoids a memory region starting at address 0, and also clarifies the documentation of the qMemoryRegionInfo packet to state that permissions: are required for all memory regions that are accessible by the inferior process. A memory region with no permissions: key means that it is not an accessible region of memory. I put this PR up at #100288
@dlav-sc what do you think? I know you've moved past this issue by implementing memory-allocation and fixing your qMemoryRegionInfo packet response, but I wanted to get a final fix before we all walk away from this PR.

@jasonmolenda
Copy link
Collaborator

I've merged #100288 I think we should close this PR.

@dlav-sc
Copy link
Contributor Author

dlav-sc commented Jul 26, 2024

I put up a PR that avoids a memory region starting at address 0, and also clarifies the documentation of the qMemoryRegionInfo packet to state that permissions: are required for all memory regions that are accessible by the inferior process. A memory region with no permissions: key means that it is not an accessible region of memory. I put this PR up at #100288 @dlav-sc what do you think? I know you've moved past this issue by implementing memory-allocation and fixing your qMemoryRegionInfo packet response, but I wanted to get a final fix before we all walk away from this PR.

I've checked your patch on my machine and it works, so looks good to me.

I've merged #100288 I think we should close this PR.

Yep, lets close this PR.

@dlav-sc dlav-sc closed this Aug 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants