Compare commits

...

2 Commits

Author SHA1 Message Date
George Joseph
bb506faaa7 ast_coredumper: Fix multiple issues
* Fixed an issue with tarball-coredumps when asterisk was invoked without an
absolute path.

* Fixed an issue with gdb itself segfaulting when trying to get symbols from
separate debuginfo files.  The command line arguments needed to be altered
such that the gdbinit files is loaded before anything else but the
`dump-asterisk` command is run after full initialization.

In the embedded gdbinit script:

* The extract_string_symbol function needed a `char *` cast to work properly.

* The s_strip function needed to be updated to continue to work with the
cpp_map_name_id channel storage backend.

* A new function was added to dump the channels when cpp_map_name_id was
used.

* The Channel object was updated to account for the new channel storage
backends

* The show_locks function was refactored to work correctly.
2025-11-13 20:51:08 +00:00
Daouda Taha
7d104d69a9 app_mixmonitor: Add 's' (skip) option to delay recording.
The 's' (skip) option delays MixMonitor recording until the specified number of seconds
(can be fractional) have elapsed since MixMonitor was invoked.

No audio is written to the recording file during this time. If the call ends before this
period, no audio will be saved. This is useful for avoiding early audio such as
announcements, ringback tones, or other non-essential sounds.

UserNote: This change introduces a new 's(<seconds>)' (skip) option to the MixMonitor
application. Example:
  MixMonitor(${UNIQUEID}.wav,s(3))

This skips recording for the first 3 seconds before writing audio to the file.
Existing MixMonitor behavior remains unchanged when the 's' option is not used.
2025-11-13 19:24:37 +00:00
2 changed files with 221 additions and 77 deletions

View File

@@ -158,6 +158,13 @@
separated by commas eg. m(1111@default,2222@default,...). Folders can be optionally specified using
the syntax: mailbox@context/folder</para>
</option>
<option name="s">
<argument name="seconds" required="true" />
<para>Don't record until <replaceable>seconds</replaceable> (can be fractional) have elapsed since MixMonitor was invoked.
No audio is written to the recording file during this time. If the call ends before this period,
no audio will be saved. This can be useful to avoid recording announcements,
ringback tones, or other non-essential early audio.</para>
</option>
</optionlist>
</parameter>
<parameter name="command">
@@ -393,6 +400,8 @@
#define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
#define MIN_SKIP_SECONDS 1
static const char * const app = "MixMonitor";
static const char * const stop_app = "StopMixMonitor";
@@ -431,6 +440,9 @@ struct mixmonitor {
);
int call_priority;
/* Number of seconds (can be fractional) to skip at the start of recording */
double skip_seconds;
/* FUTURE DEVELOPMENT NOTICE
* recipient_list will need locks if we make it editable after the monitor is started */
AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
@@ -455,6 +467,7 @@ enum mixmonitor_flags {
MUXFLAG_AUTO_DELETE = (1 << 16),
MUXFLAG_REAL_CALLERID = (1 << 17),
MUXFLAG_INTERLEAVED = (1 << 18),
MUXFLAG_SKIP = (1 << 19),
};
enum mixmonitor_args {
@@ -468,6 +481,7 @@ enum mixmonitor_args {
OPT_ARG_BEEP_INTERVAL,
OPT_ARG_DEPRECATED_RWSYNC,
OPT_ARG_NO_RWSYNC,
OPT_ARG_SKIP,
OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
};
@@ -489,6 +503,7 @@ AST_APP_OPTIONS(mixmonitor_opts, {
AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
AST_APP_OPTION_ARG('S', MUXFLAG_DEPRECATED_RWSYNC, OPT_ARG_DEPRECATED_RWSYNC),
AST_APP_OPTION_ARG('n', MUXFLAG_NO_RWSYNC, OPT_ARG_NO_RWSYNC),
AST_APP_OPTION_ARG('s', MUXFLAG_SKIP, OPT_ARG_SKIP),
});
struct mixmonitor_ds {
@@ -772,6 +787,8 @@ static void *mixmonitor_thread(void *obj)
int errflag = 0;
struct ast_format *format_slin;
struct timeval skip_start = ast_tvnow();
/* Keep callid association before any log messages */
if (mixmonitor->callid) {
ast_callid_threadassoc_add(mixmonitor->callid);
@@ -792,6 +809,11 @@ static void *mixmonitor_thread(void *obj)
ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
if (mixmonitor->skip_seconds > 0.0) {
ast_debug(3, "%s skipping initial %.3f seconds\n",
mixmonitor->name, mixmonitor->skip_seconds);
}
/* The audiohook must enter and exit the loop locked */
ast_audiohook_lock(&mixmonitor->audiohook);
while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
@@ -817,6 +839,22 @@ static void *mixmonitor_thread(void *obj)
|| mixmonitor_autochan_is_bridged(mixmonitor->autochan)) {
ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
/* Skip writing audio for the first N seconds */
if (mixmonitor->skip_seconds > 0.0) {
struct timeval now = ast_tvnow();
double elapsed = ast_tvdiff_ms(now, skip_start) / 1000.0;
if (elapsed < mixmonitor->skip_seconds) {
ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
/* Skip this frame and continue */
goto frame_cleanup;
} else {
ast_debug(3, "%s skip period %.3f seconds elapsed; starting to write audio\n",
mixmonitor->name, mixmonitor->skip_seconds);
mixmonitor->skip_seconds = 0.0;
}
}
/* Write out the frame(s) */
if ((*fs_read) && (fr_read)) {
struct ast_frame *cur;
@@ -883,6 +921,8 @@ static void *mixmonitor_thread(void *obj)
}
ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
}
frame_cleanup:
/* All done! free it. */
if (fr) {
ast_frame_free(fr, 0);
@@ -1038,7 +1078,7 @@ static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
unsigned int flags, int readvol, int writevol,
const char *post_process, const char *filename_write,
char *filename_read, const char *uid_channel_var,
const char *recipients, const char *beep_id)
const char *recipients, const char *beep_id, double skip_seconds)
{
pthread_t thread;
struct mixmonitor *mixmonitor;
@@ -1080,6 +1120,7 @@ static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
/* Copy over flags and channel name */
mixmonitor->flags = flags;
mixmonitor->skip_seconds = skip_seconds;
if (!(mixmonitor->autochan = ast_autochan_setup(chan))) {
mixmonitor_free(mixmonitor);
return -1;
@@ -1235,6 +1276,7 @@ static char *filename_parse(char *filename, char *buffer, size_t len)
static int mixmonitor_exec(struct ast_channel *chan, const char *data)
{
int x, readvol = 0, writevol = 0;
double skip_seconds = 0.0;
char *filename_read = NULL;
char *filename_write = NULL;
char filename_buffer[1024] = "";
@@ -1334,6 +1376,22 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
return -1;
}
}
if (ast_test_flag(&flags, MUXFLAG_SKIP)) {
if (ast_strlen_zero(opts[OPT_ARG_SKIP])) {
ast_log(LOG_WARNING, "No skip value provided for the 's' (skip) option; skipping will be ignored as no default exists.\n");
} else {
char *endptr = NULL;
double val = strtod(opts[OPT_ARG_SKIP], &endptr);
if (endptr == opts[OPT_ARG_SKIP] || *endptr != '\0') {
ast_log(LOG_WARNING, "Skip value '%s' is not a valid number; ignoring skip.\n", opts[OPT_ARG_SKIP]);
} else if (val < (double) MIN_SKIP_SECONDS) {
ast_log(LOG_WARNING, "Skip value %.3f is below minimum %d; ignoring skip.\n", val, MIN_SKIP_SECONDS);
} else {
skip_seconds = val;
}
}
}
}
/* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
@@ -1361,7 +1419,8 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
filename_read,
uid_channel_var,
recipients,
beep_id)) {
beep_id,
skip_seconds)) {
ast_module_unref(ast_module_info->self);
}

View File

@@ -266,6 +266,8 @@ for i in "${!COREDUMPS[@]}" ; do
libdir=$(dirname "${libfile}")
}
[ "$(dirname "${astbin}")" == "." ] && astbin="$(which "${astbin}")" || :
astbin=$(realpath -e "${astbin}")
msg " ASTBIN: $astbin"
msg " MODDIR: $moddir"
msg " ETCDIR: $etcdir"
@@ -288,7 +290,7 @@ for i in "${!COREDUMPS[@]}" ; do
cfname=$(basename "${cf}")
# Produce all the output files
${GDB} -n --batch -q --ex "source $gdbinit" "${astbin}" "$cf" 2>/dev/null | (
${GDB} -n --batch -q --iex "source $gdbinit" -ex "dump-asterisk" "${astbin}" "$cf" 2>/dev/null | (
of=/dev/null
while IFS= read -r line ; do
if [[ "$line" =~ !@!@!@!\ ([^\ ]+)\ !@!@!@! ]] ; then
@@ -507,7 +509,7 @@ extract_binary_name() {
# shellcheck disable=SC2317
extract_string_symbol() {
${GDB} "$1" "$2" -q --batch \
-ex "p $3" 2>/dev/null \
-ex "p (char *)$3" 2>/dev/null \
| sed -n -r -e 's/[$]1\s*=\s*[0-9a-fx]+\s+<[^>]+>\s+"([^"]+)"/\1/gp'
return 0
}
@@ -771,6 +773,12 @@ def s_strip(value):
except:
pass
try:
if value.type.code in [ gdb.TYPE_CODE_ARRAY, gdb.TYPE_CODE_PTR ]:
return value.string()
except:
pass
return str(value).strip('" ') or "<None>"
@@ -898,6 +906,64 @@ def get_container_rbtree_objects(name, type, on_object=None):
return objs
def get_container_count(name):
return int(gdb.parse_and_eval(name).dereference()['elements'])
def get_container_map_objects(name, type, on_object=None):
"""Retrieve a list of objects from a C++ map.
Expected on_object signature:
res, stop = on_object(GDB Value)
The given callback, on_object, is called for each object found in the
map. The callback is passed a dereferenced GDB Value object and
expects an object to be returned, which is then appended to a list of
objects to be returned by this function. Iteration can be stopped by
returning "True" for the second return value.
If on_object is not specified then the dereferenced GDB value is instead
added directly to the returned list.
Args:
name: The name of the map
type: The type of objects stored in the container
on_object: Optional function called on each object found
Return:
A list of map objects
"""
objs = []
map = gdb.parse_and_eval(name)
node = map['_M_t']['_M_impl']['_M_header']['_M_left']
tree_size = map['_M_t']['_M_impl']['_M_node_count']
for i in range(0, tree_size):
obj_node = (node + 2)
obj_p = obj_node.cast(gdb.lookup_type(type).pointer().pointer()).dereference()
obj = obj_p.dereference()
res, stop = on_object(obj) if on_object else (obj, False)
if res:
objs.append(res)
if stop:
return objs
if node['_M_right'] != 0:
node = node['_M_right']
while node['_M_left'] != 0:
node = node['_M_left']
else:
tmp_node = node['_M_parent']
while node == tmp_node['_M_right']:
node = tmp_node
tmp_node = tmp_node['_M_parent']
if node['_M_right'] != tmp_node:
node = tmp_node
return objs
def get_map_count(name):
map = gdb.parse_and_eval(name)
return map['_M_t']['_M_impl']['_M_node_count']
def build_info():
@@ -969,7 +1035,7 @@ class TaskProcessor(object):
def __init__(self, name, processed, in_queue, max_depth,
low_water, high_water):
self.name = s_strip(name)
self.name = str(name).strip('"')
self.processed = int(processed)
self.in_queue = int(in_queue)
self.max_depth = int(max_depth)
@@ -979,7 +1045,7 @@ class TaskProcessor(object):
class Channel(object):
template = ("{name:30} {context:>20} {exten:>20} {priority:>10} {state:>25} "
template = ("{name:42} {context:>20} {exten:>20} {priority:>10} {state:>25} "
"{app:>20} {data:>30} {caller_id:>15} {created:>30} "
"{account_code:>15} {peer_account:>15} {bridge_id:>38}")
@@ -989,14 +1055,22 @@ class Channel(object):
'account_code': 'Accountcode', 'peer_account': 'PeerAccount',
'bridge_id': 'BridgeID'}
container = 'current_channel_storage_instance->handle->handle'
map = '(((struct mni_channelstorage_driver_pvt *)current_channel_storage_instance->handle)->by_name)'
@staticmethod
def objects():
try:
objs = get_container_hash_objects('channels',
'struct ast_channel', Channel.from_value)
objs.sort(key=lambda x: x.name.lower())
driver = gdb.parse_and_eval("current_channel_storage_driver")
driver_name = driver['driver_name'].string()
if driver_name == "cpp_map_name_id":
objs = get_container_map_objects(Channel.map,
'struct ast_channel', Channel.from_value)
else:
objs = get_container_hash_objects(Channel.container,
'struct ast_channel', Channel.from_value)
objs.sort(key=lambda x: x.name.lower())
except:
return []
@@ -1025,13 +1099,19 @@ class Channel(object):
@staticmethod
def summary():
driver = gdb.parse_and_eval("current_channel_storage_driver")
driver_name = driver['driver_name'].string()
if driver_name == "cpp_map_name_id":
count = get_map_count(Channel.map)
else:
count = get_container_count(Channel.container)
try:
return ("{0} active channels\n"
"{1} active calls\n"
"{2} calls processed".format(
int(gdb.parse_and_eval(
'channels').dereference()['elements']),
return ("Channel Driver Name: {0}\n"
"{1} active channels\n"
"{2} active calls\n"
"{3} calls processed".format(driver_name,
count,
get("countcalls"),
get("totalcalls")))
except:
@@ -1197,76 +1277,81 @@ DumpAsteriskCommand ()
end
define show_locks
set $n = lock_infos.first
set $n = lock_infos.first
if $argc == 0
printf " where_held count-|\n"
printf " suspended-| |\n"
printf " type- | times locked-| | |\n"
printf "thread status file line function lock name | lock addr | | |\n"
else
printf "thread,status,file,line,function,lock_name,lock_type,lock_addr,times_locked,suspended,where_held_count,where_held_file,where_held_line,where_held_function,there_held_thread\n"
end
if $argc == 0
printf "%s\n", " where_held count-|"
printf "%s\n", " suspended-| |"
printf "%s\n", " type-| times locked-| | |"
printf "%-14s %-36s %6s %-42s %-8s %-36s %3s %-14s %3s %3s %3s\n",\
"thread","file","line","function","status","lock name","|","lock addr","|","|","|"
printf "%s\n", "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
while $n
if $n->num_locks > 0
else
printf "thread,status,file,line,function,lock_name,lock_type,lock_addr,times_locked,suspended,where_held_count,where_held_file,where_held_line,where_held_function,there_held_thread\n"
end
while $n
if $n->num_locks > 0
set $i = 0
while $i < $n->num_locks
if $n->locks[$i]->suspended == 0
if ((ast_mutex_t *)$n->locks[$i]->lock_addr)->tracking
if $n->locks[$i]->type > 0
set $track = ((ast_rwlock_t *)$n->locks[$i]->lock_addr)->track
else
set $track = ((ast_mutex_t *)$n->locks[$i]->lock_addr)->track
end
end
set $reentrancy = $track->reentrancy
set $pending = $n->locks[$i]->pending
if $argc > 0
printf "%p,%d,%s,%d,%s,%s,%d,%p,%d,%d,%d",\
$n->thread_id, $n->locks[$i]->pending, $n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func,\
$n->locks[$i]->lock_name, $n->locks[$i]->type, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $track->reentrancy
if $reentrancy
if $pending
printf ",%s,%d,%s,%p", $track->file[0], $track->lineno[0], $track->func[0], $track->thread[0]
end
end
if $n->locks[$i]->suspended != 47
if $n->locks[$i]->pending > 0
set $status = "waiting"
end
if $n->locks[$i]->pending < 0
set $status = "failed"
end
if $n->locks[$i]->pending == 0
set $status = "holding"
end
if $n->locks[$i]->type == 0
set $ltype = "M"
end
if $n->locks[$i]->type == 1
set $ltype = "RD"
end
if $n->locks[$i]->type == 2
set $ltype = "WR"
end
if ((ast_mutex_t *)$n->locks[$i]->lock_addr)->track
if $n->locks[$i]->type > 0
set $track = ((ast_rwlock_t *)$n->locks[$i]->lock_addr)->track
else
if $n->locks[$i]->pending < 0
printf "%p failed %-20s %6d %-36s %-20s %d %14p %3d %d %d",\
$n->thread_id,\
$n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func,\
$n->locks[$i]->lock_name, $n->locks[$i]->type, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $track->reentrancy
end
if $n->locks[$i]->pending == 0
printf "%p holding %-20s %6d %-36s %-20s %d %14p %3d %d %d",\
$n->thread_id,\
$n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func,\
$n->locks[$i]->lock_name, $n->locks[$i]->type, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $track->reentrancy
end
if $n->locks[$i]->pending > 0
printf "%p waiting %-20s %6d %-36s %-20s %d %14p %3d %d %d",\
$n->thread_id,\
$n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func,\
$n->locks[$i]->lock_name, $n->locks[$i]->type, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $track->reentrancy
end
if $reentrancy
if $pending
printf "\n held at: %-20s %6d %-36s by 0x%08lx", $track->file[0], $track->lineno[0], $track->func[0], $track->thread_id[0]
end
end
set $track = ((ast_mutex_t *)$n->locks[$i]->lock_addr)->track
end
printf "\n"
end
set $i = $i + 1
end
if $track
set $reentrancy = $track->reentrancy
else
set $reentrancy = 0
end
set $pending = $n->locks[$i]->pending
if $argc > 0
printf "%p,%d,%s,%d,%s,%s,%d,%p,%d,%d,%d",\
$n->thread_id, $n->locks[$i]->pending, $n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func,\
$n->locks[$i]->lock_name, $n->locks[$i]->type, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $reentrancy
if $reentrancy && $pending
printf ",%s,%d,%s,%p", $track->file[0], $track->lineno[0], $track->func[0], $track->thread[0]
end
else
printf "%14p %-36s %6d %-42s %-8s %-36s %3s %-14p %3d %3d %3d", \
$n->thread_id, $n->locks[$i]->file, $n->locks[$i]->line_num, $n->locks[$i]->func, $status, \
$n->locks[$i]->lock_name, $ltype, $n->locks[$i]->lock_addr, $n->locks[$i]->times_locked,\
$n->locks[$i]->suspended, $reentrancy
if $reentrancy && $pending
printf "\n held at: %s:%d %s() by 0x%08lx", \
$track->file[0], $track->lineno[0], $track->func[0], $track->thread_id[0]
end
end
printf "\n"
end
set $i = $i + 1
end
end
set $n = $n->entry->next
end
end
dump-asterisk