On some architectures like m68k, alignof(void*) is 2, not sizeof(void*)
(which is 4). So the natural alignment of struct Option is 2 and
sizeof(Option) == 26.
However, each variable placed in the SYSTEMD_OPTIONS ELF section via
_OPTION() carries _alignptr_ (= __attribute__((aligned(sizeof(void*))))),
which forces each entry to start at a 4-byte boundary. The linker
therefore inserts 2 bytes of padding between adjacent entries, producing
an actual stride of 28 in the section.
option_parse() iterates over the section with pointer arithmetic
("opt++"), which advances by sizeof(Option) == 26 and lands inside the
padding. The fields read back as zero, and since commit cf88903637
("tree-wide: get rid of most uses of ALIGN_PTR") added the ordering
assertion below, the resulting "0 < 0" trips it when running --help:
Assertion 'opt->id < (opt + 1)->id' failed at src/shared/options.c:116, function option_parse(). Aborting.
#0 0xc0a7b248 in ?? () from /usr/lib/m68k-linux-gnu/libc.so.6
#1 0xc0a7b2ce in pthread_kill () from /usr/lib/m68k-linux-gnu/libc.so.6
#2 0xc0a2edc6 in raise () from /usr/lib/m68k-linux-gnu/libc.so.6
#3 0xc0a1c128 in abort () from /usr/lib/m68k-linux-gnu/libc.so.6
#4 0xc05c6f78 in option_parse (options=0x0, options_end=0x0, state=0xc09ca968) at ../src/shared/options.c:116
Fix this by applying _alignptr_ to the struct definition itself, so that
sizeof(Option) is padded up to a multiple of sizeof(void*) and matches
the actual on-disk stride. Add an assert_cc() so any future regression
is caught at compile time.
The same latent bug applies to Verb and TestFunc, which use the same
section-placement pattern. Their natural sizeof was already a multiple
of sizeof(void*) so no crash was observed, but apply the same fix
defensively.
Follow-up for cf88903637
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
110 lines
5.5 KiB
C
110 lines
5.5 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
#pragma once
|
|
|
|
#include "shared-forward.h"
|
|
|
|
#define VERB_ANY (UINT_MAX)
|
|
|
|
typedef enum VerbFlags {
|
|
VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */
|
|
VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */
|
|
VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */
|
|
} VerbFlags;
|
|
|
|
/* Note: see the comment on struct Option in options.h for why _alignptr_ is required here. */
|
|
typedef struct _alignptr_ {
|
|
const char *verb;
|
|
unsigned min_args, max_args;
|
|
VerbFlags flags;
|
|
int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata);
|
|
uintptr_t data;
|
|
const char *argspec;
|
|
const char *help;
|
|
} Verb;
|
|
assert_cc(sizeof(Verb) % sizeof(void*) == 0);
|
|
|
|
#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \
|
|
_section_("SYSTEMD_VERBS") \
|
|
_alignptr_ \
|
|
_used_ \
|
|
_retain_ \
|
|
_no_reorder_ \
|
|
_variable_no_sanitize_address_ \
|
|
static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \
|
|
.verb = v, \
|
|
.min_args = amin, \
|
|
.max_args = amax, \
|
|
.flags = f, \
|
|
.dispatch = d, \
|
|
.data = dat, \
|
|
.argspec = a, \
|
|
.help = h, \
|
|
}
|
|
|
|
/* Forward-define function d. scope specifies the scope, e.g. static. */
|
|
#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h) \
|
|
DISABLE_WARNING_REDUNDANT_DECLS \
|
|
scope int d(int, char**, uintptr_t, void*); \
|
|
REENABLE_WARNING \
|
|
_VERB_DATA(d, v, a, amin, amax, f, dat, h)
|
|
/* The same as VERB_SCOPE_FULL with scope hardwired to 'static'. */
|
|
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
|
|
VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h)
|
|
|
|
/* The same as VERB_SCOPE_FULL/VERB_FULL, but without the data argument. */
|
|
#define VERB_SCOPE(scope, d, v, a, amin, amax, f, h) \
|
|
VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h)
|
|
#define VERB(d, v, a, amin, amax, f, h) \
|
|
VERB_SCOPE(static, d, v, a, amin, amax, f, h)
|
|
|
|
/* Simplified VERB_SCOPE/VERB for verbs that take no argument. */
|
|
#define VERB_SCOPE_NOARG(scope, d, v, h) \
|
|
VERB_SCOPE(scope, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
|
|
#define VERB_NOARG(d, v, h) \
|
|
VERB_SCOPE_NOARG(static, d, v, h)
|
|
#define VERB_DEFAULT_NOARG(d, v, h) \
|
|
VERB_SCOPE(static, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ VERB_DEFAULT, h)
|
|
|
|
/* Magic entry in the table (which will not be returned) that designates the start of the group <gr>.
|
|
* The macro works as a separator between groups and must be between other VERB* stanzas. */
|
|
#define VERB_GROUP(gr) \
|
|
_VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \
|
|
/* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL)
|
|
|
|
/* This is magically mapped to the beginning and end of the section */
|
|
extern const Verb __start_SYSTEMD_VERBS[];
|
|
extern const Verb __stop_SYSTEMD_VERBS[];
|
|
|
|
bool running_in_chroot_or_offline(void);
|
|
|
|
bool should_bypass(const char *env_prefix);
|
|
|
|
const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]);
|
|
|
|
int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata);
|
|
#define dispatch_verb(args, userdata) \
|
|
_dispatch_verb(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, userdata)
|
|
|
|
int _verbs_get_help_table(
|
|
const Verb verbs[],
|
|
const Verb verbs_end[],
|
|
const char *group,
|
|
Table **ret);
|
|
#define verbs_get_help_table_group(group, ret) \
|
|
_verbs_get_help_table(__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, group, ret)
|
|
#define verbs_get_help_table(ret) \
|
|
verbs_get_help_table_group(/* group= */ NULL, ret)
|
|
|
|
#define _VERB_COMMON_HELP_IMPL(impl) \
|
|
static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \
|
|
return impl(); \
|
|
}
|
|
|
|
#define VERB_COMMON_HELP(impl) \
|
|
VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \
|
|
_VERB_COMMON_HELP_IMPL(impl)
|
|
|
|
#define VERB_COMMON_HELP_HIDDEN(impl) \
|
|
VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \
|
|
_VERB_COMMON_HELP_IMPL(impl)
|