libroot: Add brk() and sbrk().

This commit replaces the placeholder implementation of sbrk(), which
operated on a process' heap, with real implementations of brk() and
sbrk() that adjust a process' program break.

* unistd.h: Add standard definitions of brk() and sbrk(); include
  stdint.h for intptr_t.
* thread.cpp: Recognize RLIMIT_AS and RLIMIT_DATA resource limits
  (both currently unlimited); order limit identifiers alphabetically.
* arch-specific.cpp: Remove sbrk_hook().
* malloc_debug_api.cpp: Remove sbrk_hook().
* unistd/Jamfile: Build brk.c instead of sbrk.c.
* unistd/brk.c: Add.
* unistd/sbrk.c: Delete (placeholder implementation).
* libroot_stubs.c: Remove sbrk_hook().
* libroot_stubs_legacy.c: Remove sbrk_hook().
* src/tests/.../posix/Jamfile: Build brk_test.c.
* brk_test.c: Add (simple unit test that demonstrates behaviour of
  sbrk()).

Signed-off-by: Jérôme Duval <[email protected]>
This commit is contained in:
Simon South
2015-11-22 17:59:22 +01:00
committed by Jérôme Duval
parent dc0347f13e
commit dbf060a3f7
11 changed files with 429 additions and 51 deletions
+4 -1
View File
@@ -7,6 +7,7 @@
#include <null.h>
#include <stdint.h>
#include <sys/types.h>
@@ -221,7 +222,9 @@ extern void _exit(int status) __attribute__ ((noreturn));
extern pid_t tcgetpgrp(int fd);
extern int tcsetpgrp(int fd, pid_t pgrpid);
extern void *sbrk(long incr);
extern int brk(void *addr);
extern void *sbrk(intptr_t increment);
extern unsigned int alarm(unsigned int seconds);
extern useconds_t ualarm(useconds_t microSeconds, useconds_t interval);
+17 -7
View File
@@ -1307,15 +1307,25 @@ common_getrlimit(int resource, struct rlimit * rlp)
return B_BAD_ADDRESS;
switch (resource) {
case RLIMIT_NOFILE:
case RLIMIT_NOVMON:
return vfs_getrlimit(resource, rlp);
case RLIMIT_AS:
rlp->rlim_cur = __HAIKU_ADDR_MAX;
rlp->rlim_max = __HAIKU_ADDR_MAX;
return B_OK;
case RLIMIT_CORE:
rlp->rlim_cur = 0;
rlp->rlim_max = 0;
return B_OK;
case RLIMIT_DATA:
rlp->rlim_cur = RLIM_INFINITY;
rlp->rlim_max = RLIM_INFINITY;
return B_OK;
case RLIMIT_NOFILE:
case RLIMIT_NOVMON:
return vfs_getrlimit(resource, rlp);
case RLIMIT_STACK:
{
rlp->rlim_cur = USER_MAIN_THREAD_STACK_SIZE;
@@ -1338,16 +1348,16 @@ common_setrlimit(int resource, const struct rlimit * rlp)
return B_BAD_ADDRESS;
switch (resource) {
case RLIMIT_NOFILE:
case RLIMIT_NOVMON:
return vfs_setrlimit(resource, rlp);
case RLIMIT_CORE:
// We don't support core file, so allow settings to 0/0 only.
if (rlp->rlim_cur != 0 || rlp->rlim_max != 0)
return EINVAL;
return B_OK;
case RLIMIT_NOFILE:
case RLIMIT_NOVMON:
return vfs_setrlimit(resource, rlp);
default:
return EINVAL;
}
@@ -36,9 +36,6 @@
# define CTRACE(x) ;
#endif
extern "C" void *(*sbrk_hook)(long);
void *(*sbrk_hook)(long) = &BPrivate::hoardSbrk;
using namespace BPrivate;
struct free_chunk {
@@ -205,15 +205,6 @@ __heap_terminate_after()
// #pragma mark - Public API
extern "C" void*
sbrk_hook(long)
{
debug_printf("sbrk not supported on malloc debug\n");
debugger("sbrk not supported on malloc debug");
return NULL;
}
extern "C" void*
memalign(size_t alignment, size_t size)
{
+1 -1
View File
@@ -12,6 +12,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
MergeObject <$(architecture)>posix_unistd.o :
access.c
alarm.c
brk.c
chown.c
chroot.cpp
close.c
@@ -33,7 +34,6 @@ for architectureObject in [ MultiArchSubDirSetup ] {
pipe.c
process.c
read.c
sbrk.c
sleep.c
sync.c
system.cpp
+268
View File
@@ -0,0 +1,268 @@
/*
* Copyright 2015 Simon South, ssouth@simonsouth.com
* All rights reserved. Distributed under the terms of the MIT License.
*/
/*!
* \file brk.c
*
* This module provides \c brk and \c sbrk, functions that adjust the program
* break of their calling process.
*
* A process' program break is the first memory location past the end of its
* data segment. Moving the program break has the effect of increasing or
* decreasing the amount of memory allocated to the process for its data and
* can be used to achieve a simple form of memory management.
*
* An ELF executable may contain multiple writable segments that make up a
* program's data segment, each of which may be placed by Haiku's \c
* runtime_loader in either one or two areas in memory. Additionally, nothing
* stops a program from adding onto its data segment directly using \c
* create_area. For these reasons this implementation avoids making
* assumptions about the process' data segment except that it is contiguous.
* \c brk and \c sbrk will function correctly regardless of the number of
* (adjacent) areas used to hold the process' data.
*
* Note this implementation never creates new areas; its operation is limited
* to expanding, contracting or deleting areas as necessary.
*/
#include <errno.h>
#include <stdint.h>
#include <sys/resource.h>
#include <unistd.h>
#include <image.h>
#include <OS.h>
#include <errno_private.h>
/*!
* Locates and returns information about the base (lowest in memory) area of
* the contiguous set of areas that hold the calling process' data segment.
*
* \param base_area_info A pointer to an \c area_info structure to receive
* information about the area.
* \return \c B_OK on success; \c B_BAD_VALUE otherwise.
*/
static status_t
get_data_segment_base_area_info(area_info *base_area_info)
{
status_t result = B_ERROR;
bool app_image_found = false;
image_info i_info;
int32 image_cookie = 0;
/* Locate our app image and output information for the area that holds the
* start of its data segment */
while (!app_image_found
&& get_next_image_info(0, &image_cookie, &i_info) == B_OK) {
if (i_info.type == B_APP_IMAGE) {
app_image_found = true;
result = get_area_info(area_for(i_info.data), base_area_info);
}
}
return result;
}
/*!
* Checks whether a proposed new program break would be valid given the
* resource limits imposed on the calling process.
*
* \param program_break The proposed new program break.
* \param base_address The base (start) address of the process' data segment.
* \return \c true if the program break would be valid given this process'
* resource limits; \c false otherwise.
*/
static bool
program_break_within_resource_limits(void *program_break, void *base_address)
{
bool result = false;
struct rlimit rlim;
if (getrlimit(RLIMIT_AS, &rlim) == 0
&& (rlim.rlim_cur == RLIM_INFINITY
|| program_break < (void *)rlim.rlim_cur)
&& getrlimit(RLIMIT_DATA, &rlim) == 0
&& (rlim.rlim_cur == RLIM_INFINITY
|| ((rlim_t)((uint8_t *)program_break - (uint8_t *)(base_address))
< rlim.rlim_cur)))
result = true;
return result;
}
/*!
* Resizes an area, up or down as necessary, so it ends at the specified
* address (rounded up to the nearest page boundary).
*
* \param a_info An \c area_info structure corresponding to the area on which
* to operate.
* \param address The new address at which the area should end. This address
* will be rounded up to the nearest page boundary.
* \return \c B_OK on success; \c B_BAD_VALUE, \c B_NO_MEMORY or \c B_ERROR
* otherwise (refer to the documentation for \c resize_area).
*/
static status_t
resize_area_to_address(area_info *a_info, void *address)
{
size_t new_size = (uint8_t *)address - (uint8_t *)(a_info->address);
size_t new_size_aligned = (new_size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
return resize_area(a_info->area, new_size_aligned);
}
/*!
* An internal method that sets the program break of the calling process to the
* specified address.
*
* \param addr The requested new program break. This address will be rounded up
* as necessary to align the program break on a page boundary.
* \param base_area_info An \c area_info structure corresponding to the base
* (lowest in memory) area of the contiguous set of areas
* that hold the calling process' data segment.
* \return 0 on success; -1 otherwise.
*/
static int
brk_internal(void *addr, area_info *base_area_info)
{
int result = 0;
void *next_area_address;
area_id next_area;
area_info next_area_info;
/* First, recursively process the next (higher) adjacent area, if any */
next_area_address = (uint8_t *)base_area_info->address
+ base_area_info->size;
next_area = area_for(next_area_address);
if (next_area != B_ERROR) {
result = get_area_info(next_area, &next_area_info) == B_OK ?
brk_internal(addr, &next_area_info) : -1;
} else {
/* This is the highest ("last") area in the data segment, so any
* increase in size needs to occur on this area */
if (addr > next_area_address) {
result = resize_area_to_address(base_area_info, addr) == B_OK ? 0
: -1;
}
}
if (result == 0) {
if (addr <= base_area_info->address) {
/* This area starts at or above the program break the caller has
* requested, so the entire area can be deleted */
result = delete_area(base_area_info->area) == B_OK ? 0 : -1;
} else if (addr < next_area_address) {
/* The requested program break lies within this area, so this area
* must be contracted */
result = resize_area_to_address(base_area_info, addr) == B_OK ? 0
: -1;
}
}
return result;
}
/*!
* Sets the calling process' program break to the specified address, if valid
* and within the limits of available resources, expanding or contracting the
* process' data segment as necessary.
*
* \param addr The requested new program break.
* \return 0 on success; -1 on error (in which case \c errno will be set to
* \c ENOMEM).
*/
int
brk(void *addr)
{
int result = -1;
area_info base_area_info;
if (get_data_segment_base_area_info(&base_area_info) == B_OK
&& addr > base_area_info.address
&& program_break_within_resource_limits(addr, base_area_info.address))
result = brk_internal(addr, &base_area_info);
if (result == -1)
__set_errno(ENOMEM);
return result;
}
/*!
* Adjusts the calling process' program break up or down by the requested
* number of bytes, if valid and within the limits of available resources,
* expanding or contracting the process' data segment as necessary.
*
* \param increment The amount, positive or negative, in bytes by which to
* adjust the program break. This value will be rounded up as
* necessary to align the program break on a page boundary.
* \return The previous program break on success; (void *)-1 on error (in which
* case \c errno will be set to \c ENOMEM).
*/
void *
sbrk(intptr_t increment)
{
void *result = NULL;
area_info base_area_info;
area_id next_area;
area_info next_area_info;
uint8_t *program_break;
uint8_t *new_program_break;
if (get_data_segment_base_area_info(&base_area_info) == B_OK) {
/* Find the current program break, which will be the memory address
* just past the end of the highest ("last") of the areas that hold the
* data segment */
next_area = base_area_info.area;
do {
if (get_area_info(next_area, &next_area_info) == B_OK) {
next_area = area_for((uint8_t *)(next_area_info.address)
+ next_area_info.size);
} else {
result = (void *)-1;
}
} while (next_area != B_ERROR && result == NULL);
if (result == NULL) {
program_break = (uint8_t *)(next_area_info.address)
+ next_area_info.size;
new_program_break = program_break + increment;
/* If the requested increment is zero, just return the address of
* the current program break; otherwise, set a new program break
* and return the address of the old one */
if (increment == 0
|| (program_break_within_resource_limits(new_program_break,
base_area_info.address)
&& brk_internal(new_program_break, &base_area_info) == 0))
result = program_break;
else
result = (void *)-1;
}
} else {
result = (void *)-1;
}
if (result == (void *)-1)
__set_errno(ENOMEM);
return result;
}
-28
View File
@@ -1,28 +0,0 @@
/*
* Copyright 2004-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <unistd.h>
#include <errno.h>
#include <errno_private.h>
/* in hoard wrapper */
extern void *(*sbrk_hook)(long);
void *
sbrk(long increment)
{
// TODO: we only support extending the heap for now using this method
if (increment <= 0)
return NULL;
if (sbrk_hook)
return (*sbrk_hook)(increment);
return NULL;
}
@@ -79,7 +79,6 @@ int opterr;
int optind;
int optopt;
int re_syntax_options;
int sbrk_hook;
int signgam;
int stderr;
int stdin;
@@ -103,7 +103,6 @@ int opterr;
int optind;
int optopt;
int re_syntax_options;
int sbrk_hook;
int signgam;
int stderr;
int stdin;
+1
View File
@@ -7,6 +7,7 @@ TARGET_WARNING_C++FLAGS_$(TARGET_PACKAGING_ARCH)
# POSIX/libc tests
SimpleTest abort_test : abort_test.cpp ;
SimpleTest SyslogTest : SyslogTest.cpp ;
SimpleTest brk_test : brk_test.c ;
SimpleTest clearenv : clearenv.cpp ;
SimpleTest dirent_test : dirent_test.cpp ;
SimpleTest flock_test : flock_test.cpp ;
+138
View File
@@ -0,0 +1,138 @@
/*
* Copyright 2015 Simon South, ssouth@simonsouth.com
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <image.h>
#include <OS.h>
struct sbrk_test {
char *name;
char *sbrk_arg_text;
intptr_t sbrk_arg;
};
static void
output_area_info(void *base_address)
{
uint8_t *next_area_address;
area_id next_area;
area_info next_area_info;
next_area = area_for(base_address);
while (next_area != B_ERROR) {
if (get_area_info(next_area, &next_area_info) == B_OK) {
next_area_address = (uint8_t *)(next_area_info.address)
+ next_area_info.size;
printf("Area ID 0x%x: Addr: %p - %p Size: 0x%04zx %s\n",
next_area_info.area, next_area_info.address,
next_area_address - 1, next_area_info.size,
next_area_info.name);
next_area = area_for(next_area_address);
} else {
fprintf(stderr, "PROBLEM: Couldn't get area info");
}
}
}
static void
output_data_segment_info(void *address)
{
puts("Current data segment layout:");
output_area_info(address);
puts("");
}
static void
output_sbrk_result(intptr_t sbrk_arg, char *sbrk_arg_text)
{
printf("\tsbrk(%s) returns %p\n", sbrk_arg_text, sbrk(sbrk_arg));
if (errno != 0) {
printf("\tError: %s\n", strerror(errno));
errno = 0;
}
}
int
main(int argc, char **argv)
{
static const uint NUM_TESTS = 7;
static struct sbrk_test sbrk_tests[] = {
{ "Get current program break", "0", 0 },
{ "Expand data segment (less than one page)", "0x7ff", 0x7ff },
{ "Expand data segment (more than one page)", "0x57ff", 0x57ff },
{
"Expand data segment (unreasonable value)",
"INTPTR_MAX",
INTPTR_MAX
},
{ "Shrink data segment (less than one page)", "-0x7ff", -0x7ff },
{ "Shrink data segment (more than one page)", "-0x27ff", -0x27ff },
{
"Shrink data segment (unreasonable value)",
"INTPTR_MIN",
INTPTR_MIN
}
};
int result = -1;
bool app_image_found = false;
image_info i_info;
int32 image_cookie = 0;
void *data_segment_address = NULL;
uint test_index;
struct sbrk_test *next_sbrk_test;
/* Find the address of our data segment */
while (!app_image_found
&& get_next_image_info(0, &image_cookie, &i_info) == B_OK) {
if (i_info.type == B_APP_IMAGE) {
app_image_found = true;
data_segment_address = i_info.data;
}
}
if (data_segment_address != NULL) {
/* Run our tests */
test_index = 0;
while (test_index < NUM_TESTS) {
next_sbrk_test = &sbrk_tests[test_index];
output_data_segment_info(data_segment_address);
printf("%s:\n", next_sbrk_test->name);
output_sbrk_result(next_sbrk_test->sbrk_arg,
next_sbrk_test->sbrk_arg_text);
printf("\n");
test_index += 1;
}
output_data_segment_info(data_segment_address);
result = 0;
} else {
fprintf(stderr, "PROBLEM: Couldn't locate data segment\n");
}
return result;
}