/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2012. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "beam_load.h"
static Range* find_range(BeamInstr* pc);
static void lookup_loc(FunctionInfo* fi, BeamInstr* pc,
BeamInstr* modp, int idx);
/*
* The following variables keep a sorted list of address ranges for
* each module. It allows us to quickly find a function given an
* instruction pointer.
*/
struct ranges {
Range* modules; /* Sorted lists of module addresses. */
Sint n; /* Number of range entries. */
Sint allocated; /* Number of allocated entries. */
erts_smp_atomic_t mid; /* Cached search start point */
};
static struct ranges r[ERTS_NUM_CODE_IX];
static erts_smp_atomic_t mem_used;
#ifdef HARD_DEBUG
static void check_consistency(struct ranges* p)
{
int i;
ASSERT(p->n <= p->allocated);
ASSERT((Uint)(p->mid - p->modules) < p->n ||
(p->mid == p->modules && p->n == 0));
for (i = 0; i < p->n; i++) {
ASSERT(p->modules[i].start <= p->modules[i].end);
ASSERT(!i || p->modules[i-1].end < p->modules[i].start);
}
}
# define CHECK(r) check_consistency(r)
#else
# define CHECK(r)
#endif /* HARD_DEBUG */
void
erts_init_ranges(void)
{
Sint i;
erts_smp_atomic_init_nob(&mem_used, 0);
for (i = 0; i < ERTS_NUM_CODE_IX; i++) {
r[i].modules = 0;
r[i].n = 0;
r[i].allocated = 0;
erts_smp_atomic_init_nob(&r[i].mid, 0);
}
}
void
erts_start_load_ranges(void)
{
ErtsCodeIndex dst = erts_loader_code_ix();
if (r[dst].modules) {
erts_smp_atomic_add_nob(&mem_used, -r[dst].allocated);
erts_free(ERTS_ALC_T_MODULE_REFS, r[dst].modules);
r[dst].modules = NULL;
}
}
void
erts_end_load_ranges(int commit)
{
ErtsCodeIndex dst = erts_loader_code_ix();
if (commit && r[dst].modules == NULL) {
Sint i;
Sint n;
/* No modules added, just clone src and remove purged code. */
ErtsCodeIndex src = erts_active_code_ix();
erts_smp_atomic_add_nob(&mem_used, r[src].n);
r[dst].modules = erts_alloc(ERTS_ALC_T_MODULE_REFS,
r[src].n * sizeof(Range));
r[dst].allocated = r[src].n;
n = 0;
for (i = 0; i < r[src].n; i++) {
Range* rp = r[src].modules+i;
if (rp->start < rp->end) {
/* Only insert a module that has not been purged. */
r[dst].modules[n] = *rp;
n++;
}
}
r[dst].n = n;
erts_smp_atomic_set_nob(&r[dst].mid,
(erts_aint_t) (r[dst].modules + n / 2));
}
}
void
erts_update_ranges(BeamInstr* code, Uint size)
{
ErtsCodeIndex dst = erts_loader_code_ix();
ErtsCodeIndex src = erts_active_code_ix();
Sint i;
Sint n;
Sint need;
if (src == dst) {
ASSERT(!erts_initialized);
/*
* During start-up of system, the indices are the same.
* Handle this by faking a source area.
*/
src = (src+1) % ERTS_NUM_CODE_IX;
if (r[src].modules) {
erts_smp_atomic_add_nob(&mem_used, -r[src].allocated);
erts_free(ERTS_ALC_T_MODULE_REFS, r[src].modules);
}
r[src] = r[dst];
r[dst].modules = 0;
}
CHECK(&r[src]);
ASSERT(r[dst].modules == NULL);
need = r[dst].allocated = r[src].n + 1;
erts_smp_atomic_add_nob(&mem_used, need);
r[dst].modules = (Range *) erts_alloc(ERTS_ALC_T_MODULE_REFS,
need * sizeof(Range));
n = 0;
for (i = 0; i < r[src].n; i++) {
Range* rp = r[src].modules+i;
if (code < rp->start) {
r[dst].modules[n].start = code;
r[dst].modules[n].end = (BeamInstr *) (((byte *)code) + size);
n++;
break;
}
if (rp->start < rp->end) {
/* Only insert a module that has not been purged. */
r[dst].modules[n] = *rp;
n++;
}
}
while (i < r[src].n) {
Range* rp = r[src].modules+i;
if (rp->start < rp->end) {
/* Only insert a module that has not been purged. */
r[dst].modules[n] = *rp;
n++;
}
i++;
}
if (n == 0 || code > r[dst].modules[n-1].start) {
r[dst].modules[n].start = code;
r[dst].modules[n].end = (BeamInstr *) (((byte *)code) + size);
n++;
}
ASSERT(n <= r[src].n+1);
r[dst].n = n;
erts_smp_atomic_set_nob(&r[dst].mid,
(erts_aint_t) (r[dst].modules + n / 2));
CHECK(&r[dst]);
CHECK(&r[src]);
}
void
erts_remove_from_ranges(BeamInstr* code)
{
Range* rp = find_range(code);
rp->end = rp->start;
}
UWord
erts_ranges_sz(void)
{
return erts_smp_atomic_read_nob(&mem_used) * sizeof(Range);
}
/*
* Find a function from the given pc and fill information in
* the FunctionInfo struct. If the full_info is non-zero, fill
* in all available information (including location in the
* source code). If no function is found, the 'current' field
* will be set to NULL.
*/
void
erts_lookup_function_info(FunctionInfo* fi, BeamInstr* pc, int full_info)
{
BeamInstr** low;
BeamInstr** high;
BeamInstr** mid;
Range* rp;
fi->current = NULL;
fi->needed = 5;
fi->loc = LINE_INVALID_LOCATION;
rp = find_range(pc);
if (rp == 0) {
return;
}
low = (BeamInstr **) (rp->start + MI_FUNCTIONS);
high = low + rp->start[MI_NUM_FUNCTIONS];
while (low < high) {
mid = low + (high-low) / 2;
if (pc < mid[0]) {
high = mid;
} else if (pc < mid[1]) {
fi->current = mid[0]+2;
if (full_info) {
BeamInstr** fp = (BeamInstr **) (rp->start +
MI_FUNCTIONS);
int idx = mid - fp;
lookup_loc(fi, pc, rp->start, idx);
}
return;
} else {
low = mid + 1;
}
}
}
static Range*
find_range(BeamInstr* pc)
{
ErtsCodeIndex active = erts_active_code_ix();
Range* low = r[active].modules;
Range* high = low + r[active].n;
Range* mid = (Range *) erts_smp_atomic_read_nob(&r[active].mid);
CHECK(&r[active]);
while (low < high) {
if (pc < mid->start) {
high = mid;
} else if (pc > mid->end) {
low = mid + 1;
} else {
erts_smp_atomic_set_nob(&r[active].mid, (erts_aint_t) mid);
return mid;
}
mid = low + (high-low) / 2;
}
return 0;
}
static void
lookup_loc(FunctionInfo* fi, BeamInstr* orig_pc, BeamInstr* modp, int idx)
{
Eterm* line = (Eterm *) modp[MI_LINE_TABLE];
Eterm* low;
Eterm* high;
Eterm* mid;
Eterm pc;
if (line == 0) {
return;
}
pc = (Eterm) (BeamInstr) orig_pc;
fi->fname_ptr = (Eterm *) (BeamInstr) line[MI_LINE_FNAME_PTR];
low = (Eterm *) (BeamInstr) line[MI_LINE_FUNC_TAB+idx];
high = (Eterm *) (BeamInstr) line[MI_LINE_FUNC_TAB+idx+1];
while (high > low) {
mid = low + (high-low) / 2;
if (pc < mid[0]) {
high = mid;
} else if (pc < mid[1]) {
int file;
int index = mid - (Eterm *) (BeamInstr) line[MI_LINE_FUNC_TAB];
if (line[MI_LINE_LOC_SIZE] == 2) {
Uint16* loc_table =
(Uint16 *) (BeamInstr) line[MI_LINE_LOC_TAB];
fi->loc = loc_table[index];
} else {
Uint32* loc_table =
(Uint32 *) (BeamInstr) line[MI_LINE_LOC_TAB];
ASSERT(line[MI_LINE_LOC_SIZE] == 4);
fi->loc = loc_table[index];
}
if (fi->loc == LINE_INVALID_LOCATION) {
return;
}
fi->needed += 3+2+3+2;
file = LOC_FILE(fi->loc);
if (file == 0) {
/* Special case: Module name with ".erl" appended */
Atom* mod_atom = atom_tab(atom_val(fi->current[0]));
fi->needed += 2*(mod_atom->len+4);
} else {
Atom* ap = atom_tab(atom_val((fi->fname_ptr)[file-1]));
fi->needed += 2*ap->len;
}
return;
} else {
low = mid + 1;
}
}
}