TLDR;
我使用自定义逻辑过度使用malloc
和free
。然而,该计划是segfault
。有什么我想念的吗?最后,我可以使用简单的malloc
和free
来调试它,但不使用任何stdio.h
功能。为什么会这样?
完整版
我使用malloc
编写了一个32位汇编程序来覆盖free
和LD_PRELOAD
。
# PURPOSE: Program to replace malloc and free using LD_PRELOAD
#
# NOTES: The programs using these routines will ask for a certain
# size of memory. We actually use more than that size, but we
# put it at the beginning, before the pointer we hand back.
# We add a Size field and an Available/Unavailable marker. So
# the memory looks like this:
#
# #########################################################
# #Available Marker#Size of Memory#Actual Memory Locations#
# #########################################################
# ^Returned pointer points
# here
#
# The pointer we return only points to the actual locations
# requested to make it easier for the calling program. It
# also allows us to change our structure without the calling
# program having to change at all.
#
# The _alloc-lib-test.c program can test it:
# Dump the output and read it using hexdump.
.section .data
# Global variables
# This points to the beginning of the memory we are managing
heap_begin:
.long 0
# This points to one location past the memory we are managing
current_break:
.long 0
# Structure information
.equ HEADER_SIZE, 8 # Size of space for memory region header
.equ HDR_AVAIL_OFFSET, 0 # Location of the "Available" flag in the
# header
.equ HDR_SIZE_OFFSET, 4 # Location of the size field in the header
# Constants
.equ UNAVAILABLE, 0 # This is the number we will use to mark space
# that has been given out
.equ AVAILABLE, 1 # This is the number we will use to mark space
# that has been returned, and is available for
# giving
.equ SYS_BRK, 45 # break system call
.equ LINUX_SYSCALL, 0x80
.section .text
# Functions
# allocate_init starts
# PURPOSE: Call this function to initialize the functions
# (specifically, this sets heap_begin and current_break).
#
# PARAMS: None
#
# RETURN: None
.globl allocate_init
.type allocate_init,@function
allocate_init:
pushl %ebp # Standard function stuff
movl %esp, %ebp
# If the brk system call is called with 0 in %ebx, it returns the last
# valid usable address
movl $SYS_BRK, %eax # Find out where the break is
movl $0, %ebx
int $LINUX_SYSCALL
incl %eax # %eax now has the last valid address, and
# we want the memory location after that
movl %eax, current_break # Store the current break
movl %eax, heap_begin # Store the current break as our first
# address. This will cause the allocate
# function to get more memory from Linux
# the first time it is run
movl %ebp, %esp # Exit the function
popl %ebp
ret
# allocate_init ends
# allocate starts
# PURPOSE: This function is used to grab a section of memory. It
# checks to see if there are any free blocks, and, if not,
# it asks Linux for a new one.
#
# PARAMS: This function has one parameter - the size of the memory
# block we want to allocate
#
# RETURN: This function returns the address of the allocated memory
# in %eax. If there is no memory available, it will return 0
# in %eax
#
# %ecx - size of the requested memory (first/only parameter)
# %eax - current memory region being examined
# %ebx - current break position
# %edx - size of current memory region
#
# We scan through each memory region starting with heap_begin. We look
# at the size of each one, and if it has been allocated. If it's big
# enough for the requested size, and its available, it grabs that one.
# If it does not find a region large enough, it asks Linux for more
# memory. In that case, it moves current_break up
.globl malloc
.type malloc,@function
# Stack position of the memory size to allocate
.equ ST_MEM_SIZE, 8
malloc:
pushl %ebp # Standard function stuff
movl %esp, %ebp
movl $0, %edi
movl heap_begin, %eax # If heap_begin is 0, call allocate_init
cmpl %eax, %edi
je heap_empty
jne heap_nonempty
heap_empty:
call allocate_init
heap_nonempty:
movl ST_MEM_SIZE(%ebp), %ecx # %ecx will hold the size we are looking
# for (which is the first and only
# parameter)
movl heap_begin, %eax # %eax will hold the current
# search location
movl current_break, %ebx # %ebx will hold the current break
alloc_loop_begin: # Here we iterate through each memory
# region
cmpl %ebx, %eax # Need more memory if these are equal
je move_to_break
# Grab the size of this memory
movl HDR_SIZE_OFFSET(%eax), %edx
# If the space is unavailable, go to the next one
cmpl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
je next_location
cmpl %edx, %ecx # If the space is available, compare the
jle allocate_here # size to the needed size. If its big
# enough, go to allocate_here
next_location:
addl $HEADER_SIZE, %eax # The total size of the memory region
addl %edx, %eax # is the sum of the size that was
# requested when this block was created
# (currently stored in %edx), plus
# another 8 bytes for the header (4 for
# the Available/Unavailable flag, and 4
# for the Size of the region). So,
# adding %edx and $8 to %eax will get
# the address of the next memory region
jmp alloc_loop_begin # Go look at the next location
allocate_here: # If we've made it here, that means that
# the region header of the region to
# allocate is in %eax
movl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax) # Mark space as unavailable
addl $HEADER_SIZE, %eax # Move %eax past the header to the
# usable memory (since that's what we
# return)
movl %ebp, %esp # Return from the function
popl %ebp
ret
# If we've made it here, that means that we have exhausted all addressable
# memory, and we need to ask for more. %ebx holds the current endpoint of the
# data, and %ecx holds its size
move_to_break: # We need to increase %ebx to where we _want_
# memory to end, so we add space for the
addl $HEADER_SIZE, %ebx # headers structure and add space to the
# break for the data requested
addl %ecx, %ebx
# Now its time to ask Linux for more memory
pushl %eax # Save needed registers
pushl %ecx
pushl %ebx
movl $SYS_BRK, %eax # Reset the break (%ebx has the requested
# break point)
int $LINUX_SYSCALL
# Under normal conditions, this should return the new break in %eax, which will
# be either 0 if it fails, or it will be equal to or larger than we asked for.
# We don't care in this program where it actually sets the break, so as long as
# %eax isn't 0, we don't care what it is
cmpl $0, %eax # Check for error conditions
je error
popl %ebx # Restore saved registers
popl %ecx
popl %eax
# Set this memory as unavailable, since we're about to give it away
movl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
# Set the size of the memory
movl %ecx, HDR_SIZE_OFFSET(%eax)
# Move %eax to the actual start of usable memory.
# %eax now holds the return value
addl $HEADER_SIZE, %eax
movl %ebx, current_break # Save the new break
movl %ebp, %esp # Return the function
popl %ebp
ret
error:
movl $0, %eax # On error, we return zero
movl %ebp, %esp
popl %ebp
ret
# allocate ends
# deallocate starts
# PURPOSE: The purpose of this function is to give back a region of
# memory to the pool after we're done using it.
#
# PARAMS: The only parameter is the address of the memory we want to
# return to the memory pool.
#
# RETURN: There is no return value
#
# If you remember, we actually hand the program the start of the memory
# that they can use, which is 8 storage locations after the actual
# start of the memory region. All we have to do is go back 8 locations
# and mark that memory as available, so that the allocate function
# knows it can use it.
.globl free
.type free,@function
# Stack position of the memory region to free
.equ ST_MEMORY_SEG, 4
free:
# Since the function is so simple, we don't need any of the fancy
# function stuff
# Get the address of the memory to free (normally this is 8(%ebp), but
# since we didn't push %ebp or move %esp to %ebp, we can just do
# 4(%esp))
movl ST_MEMORY_SEG(%esp), %eax
# Get the pointer to the real beginning of the memory
subl $HEADER_SIZE, %eax
# Mark it as available
movl $AVAILABLE, HDR_AVAIL_OFFSET(%eax)
ret
# deallocate ends
这是在64位系统中交叉编译的,如下所示:
as --32 alloc-lib.asm -o alloc-lib.o
ld --shared "-melf_i386" alloc-lib.o -o alloc-lib.so
然而,当我在32位系统上使用以下内容测试它时,它只会导致segfault
:
LD_PRELOAD=./alloc-lib.so ls
我决定编写一个简单的C程序来分配一个整数1来测试它,但是甚至不能进行printf
函数调用。它segfault
也是!
最后,我修改了C程序,使用putchar
从新内存位置将字节转储到文件,并使用hexdump
验证该值。 putchar
API
在没有segfault
的情况下工作。
#include <stdlib.h>
#include <stdio.h>
int main(){
int *ptra;
char *cptra;
ptra = malloc(4);
*ptra = 1;
cptra = ptra;
putchar(*cptra);
putchar(*(cptra + 1));
putchar(*(cptra + 2));
putchar(*(cptra + 3));
free(ptra);
}
segfault
与ls
和printf
功能调用?假设这可能是因为我没有实现realloc
等,那么程序是否只使用标准realloc
?malloc
,free
分配整数,将其转储到文件并使用hexdump
进行验证?这是Programming from Ground Up书中的练习。