Commit 28bbfc3a authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'btf-api-extensions'

Andrii Nakryiko says:

====================
This patchset introduces a set of new APIs that make it possible to work with BTF
more effectively (and without involving kernel) for applications like pahole that
need to manipulate .BTF and .BTF.ext data.

Patch #1 changes existing btf__new() API call to only load and initialize
struct btf, while exposing new btf__load() API to attempt to load and validate
BTF in kernel.

Patch #2 adds btf__get_raw_data() API allowing to get access to raw BTF data from
struct btf.

Patch #3 adds similar btf_ext__get_raw_data() API for working with struct btf_ext.

Patch #4 removes not-yet-stable btf__get_strings() API which was added to be able
to test contents of struct btf for btf__dedup(). It's now superseded by raw APIs.

v3->v4:
- formatting fixes
- renamed btf_ext functions/structs to use "setup" language instead of "copy"
- removed btf__get_strings from libbpf.map

v2->v3:
- const void* variants of btf__get_raw_data()
- added btf_ext__get_raw_data()
- removed btf__get_strings() and adapted test_btf.c to use btf__get_raw_data()

v1->v2:
- btf_load() returns just error, not fd
- fix ordering in libbpf.map
====================
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents a4021a35 49b57e0d
...@@ -42,9 +42,8 @@ struct btf { ...@@ -42,9 +42,8 @@ struct btf {
struct btf_ext_info { struct btf_ext_info {
/* /*
* info points to a deep copy of the individual info section * info points to the individual info section (e.g. func_info and
* (e.g. func_info and line_info) from the .BTF.ext. * line_info) from the .BTF.ext. It does not include the __u32 rec_size.
* It does not include the __u32 rec_size.
*/ */
void *info; void *info;
__u32 rec_size; __u32 rec_size;
...@@ -52,8 +51,13 @@ struct btf_ext_info { ...@@ -52,8 +51,13 @@ struct btf_ext_info {
}; };
struct btf_ext { struct btf_ext {
union {
struct btf_ext_header *hdr;
void *data;
};
struct btf_ext_info func_info; struct btf_ext_info func_info;
struct btf_ext_info line_info; struct btf_ext_info line_info;
__u32 data_size;
}; };
struct btf_ext_info_sec { struct btf_ext_info_sec {
...@@ -367,8 +371,6 @@ void btf__free(struct btf *btf) ...@@ -367,8 +371,6 @@ void btf__free(struct btf *btf)
struct btf *btf__new(__u8 *data, __u32 size) struct btf *btf__new(__u8 *data, __u32 size)
{ {
__u32 log_buf_size = 0;
char *log_buf = NULL;
struct btf *btf; struct btf *btf;
int err; int err;
...@@ -378,15 +380,6 @@ struct btf *btf__new(__u8 *data, __u32 size) ...@@ -378,15 +380,6 @@ struct btf *btf__new(__u8 *data, __u32 size)
btf->fd = -1; btf->fd = -1;
log_buf = malloc(BPF_LOG_BUF_SIZE);
if (!log_buf) {
err = -ENOMEM;
goto done;
}
*log_buf = 0;
log_buf_size = BPF_LOG_BUF_SIZE;
btf->data = malloc(size); btf->data = malloc(size);
if (!btf->data) { if (!btf->data) {
err = -ENOMEM; err = -ENOMEM;
...@@ -396,17 +389,6 @@ struct btf *btf__new(__u8 *data, __u32 size) ...@@ -396,17 +389,6 @@ struct btf *btf__new(__u8 *data, __u32 size)
memcpy(btf->data, data, size); memcpy(btf->data, data, size);
btf->data_size = size; btf->data_size = size;
btf->fd = bpf_load_btf(btf->data, btf->data_size,
log_buf, log_buf_size, false);
if (btf->fd == -1) {
err = -errno;
pr_warning("Error loading BTF: %s(%d)\n", strerror(errno), errno);
if (log_buf && *log_buf)
pr_warning("%s\n", log_buf);
goto done;
}
err = btf_parse_hdr(btf); err = btf_parse_hdr(btf);
if (err) if (err)
goto done; goto done;
...@@ -418,8 +400,6 @@ struct btf *btf__new(__u8 *data, __u32 size) ...@@ -418,8 +400,6 @@ struct btf *btf__new(__u8 *data, __u32 size)
err = btf_parse_type_sec(btf); err = btf_parse_type_sec(btf);
done: done:
free(log_buf);
if (err) { if (err) {
btf__free(btf); btf__free(btf);
return ERR_PTR(err); return ERR_PTR(err);
...@@ -428,16 +408,45 @@ struct btf *btf__new(__u8 *data, __u32 size) ...@@ -428,16 +408,45 @@ struct btf *btf__new(__u8 *data, __u32 size)
return btf; return btf;
} }
int btf__load(struct btf *btf)
{
__u32 log_buf_size = BPF_LOG_BUF_SIZE;
char *log_buf = NULL;
int err = 0;
if (btf->fd >= 0)
return -EEXIST;
log_buf = malloc(log_buf_size);
if (!log_buf)
return -ENOMEM;
*log_buf = 0;
btf->fd = bpf_load_btf(btf->data, btf->data_size,
log_buf, log_buf_size, false);
if (btf->fd < 0) {
err = -errno;
pr_warning("Error loading BTF: %s(%d)\n", strerror(errno), errno);
if (*log_buf)
pr_warning("%s\n", log_buf);
goto done;
}
done:
free(log_buf);
return err;
}
int btf__fd(const struct btf *btf) int btf__fd(const struct btf *btf)
{ {
return btf->fd; return btf->fd;
} }
void btf__get_strings(const struct btf *btf, const char **strings, const void *btf__get_raw_data(const struct btf *btf, __u32 *size)
__u32 *str_len)
{ {
*strings = btf->strings; *size = btf->data_size;
*str_len = btf->hdr->str_len; return btf->data;
} }
const char *btf__name_by_offset(const struct btf *btf, __u32 offset) const char *btf__name_by_offset(const struct btf *btf, __u32 offset)
...@@ -584,7 +593,7 @@ int btf__get_map_kv_tids(const struct btf *btf, const char *map_name, ...@@ -584,7 +593,7 @@ int btf__get_map_kv_tids(const struct btf *btf, const char *map_name,
return 0; return 0;
} }
struct btf_ext_sec_copy_param { struct btf_ext_sec_setup_param {
__u32 off; __u32 off;
__u32 len; __u32 len;
__u32 min_rec_size; __u32 min_rec_size;
...@@ -592,20 +601,14 @@ struct btf_ext_sec_copy_param { ...@@ -592,20 +601,14 @@ struct btf_ext_sec_copy_param {
const char *desc; const char *desc;
}; };
static int btf_ext_copy_info(struct btf_ext *btf_ext, static int btf_ext_setup_info(struct btf_ext *btf_ext,
__u8 *data, __u32 data_size, struct btf_ext_sec_setup_param *ext_sec)
struct btf_ext_sec_copy_param *ext_sec)
{ {
const struct btf_ext_header *hdr = (struct btf_ext_header *)data;
const struct btf_ext_info_sec *sinfo; const struct btf_ext_info_sec *sinfo;
struct btf_ext_info *ext_info; struct btf_ext_info *ext_info;
__u32 info_left, record_size; __u32 info_left, record_size;
/* The start of the info sec (including the __u32 record_size). */ /* The start of the info sec (including the __u32 record_size). */
const void *info; void *info;
/* data and data_size do not include btf_ext_header from now on */
data = data + hdr->hdr_len;
data_size -= hdr->hdr_len;
if (ext_sec->off & 0x03) { if (ext_sec->off & 0x03) {
pr_debug(".BTF.ext %s section is not aligned to 4 bytes\n", pr_debug(".BTF.ext %s section is not aligned to 4 bytes\n",
...@@ -613,16 +616,15 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext, ...@@ -613,16 +616,15 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext,
return -EINVAL; return -EINVAL;
} }
if (data_size < ext_sec->off || info = btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off;
ext_sec->len > data_size - ext_sec->off) { info_left = ext_sec->len;
if (btf_ext->data + btf_ext->data_size < info + ext_sec->len) {
pr_debug("%s section (off:%u len:%u) is beyond the end of the ELF section .BTF.ext\n", pr_debug("%s section (off:%u len:%u) is beyond the end of the ELF section .BTF.ext\n",
ext_sec->desc, ext_sec->off, ext_sec->len); ext_sec->desc, ext_sec->off, ext_sec->len);
return -EINVAL; return -EINVAL;
} }
info = data + ext_sec->off;
info_left = ext_sec->len;
/* At least a record size */ /* At least a record size */
if (info_left < sizeof(__u32)) { if (info_left < sizeof(__u32)) {
pr_debug(".BTF.ext %s record size not found\n", ext_sec->desc); pr_debug(".BTF.ext %s record size not found\n", ext_sec->desc);
...@@ -634,7 +636,7 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext, ...@@ -634,7 +636,7 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext,
if (record_size < ext_sec->min_rec_size || if (record_size < ext_sec->min_rec_size ||
record_size & 0x03) { record_size & 0x03) {
pr_debug("%s section in .BTF.ext has invalid record size %u\n", pr_debug("%s section in .BTF.ext has invalid record size %u\n",
ext_sec->desc, record_size); ext_sec->desc, record_size);
return -EINVAL; return -EINVAL;
} }
...@@ -680,42 +682,35 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext, ...@@ -680,42 +682,35 @@ static int btf_ext_copy_info(struct btf_ext *btf_ext,
ext_info = ext_sec->ext_info; ext_info = ext_sec->ext_info;
ext_info->len = ext_sec->len - sizeof(__u32); ext_info->len = ext_sec->len - sizeof(__u32);
ext_info->rec_size = record_size; ext_info->rec_size = record_size;
ext_info->info = malloc(ext_info->len); ext_info->info = info + sizeof(__u32);
if (!ext_info->info)
return -ENOMEM;
memcpy(ext_info->info, info + sizeof(__u32), ext_info->len);
return 0; return 0;
} }
static int btf_ext_copy_func_info(struct btf_ext *btf_ext, static int btf_ext_setup_func_info(struct btf_ext *btf_ext)
__u8 *data, __u32 data_size)
{ {
const struct btf_ext_header *hdr = (struct btf_ext_header *)data; struct btf_ext_sec_setup_param param = {
struct btf_ext_sec_copy_param param = { .off = btf_ext->hdr->func_info_off,
.off = hdr->func_info_off, .len = btf_ext->hdr->func_info_len,
.len = hdr->func_info_len,
.min_rec_size = sizeof(struct bpf_func_info_min), .min_rec_size = sizeof(struct bpf_func_info_min),
.ext_info = &btf_ext->func_info, .ext_info = &btf_ext->func_info,
.desc = "func_info" .desc = "func_info"
}; };
return btf_ext_copy_info(btf_ext, data, data_size, &param); return btf_ext_setup_info(btf_ext, &param);
} }
static int btf_ext_copy_line_info(struct btf_ext *btf_ext, static int btf_ext_setup_line_info(struct btf_ext *btf_ext)
__u8 *data, __u32 data_size)
{ {
const struct btf_ext_header *hdr = (struct btf_ext_header *)data; struct btf_ext_sec_setup_param param = {
struct btf_ext_sec_copy_param param = { .off = btf_ext->hdr->line_info_off,
.off = hdr->line_info_off, .len = btf_ext->hdr->line_info_len,
.len = hdr->line_info_len,
.min_rec_size = sizeof(struct bpf_line_info_min), .min_rec_size = sizeof(struct bpf_line_info_min),
.ext_info = &btf_ext->line_info, .ext_info = &btf_ext->line_info,
.desc = "line_info", .desc = "line_info",
}; };
return btf_ext_copy_info(btf_ext, data, data_size, &param); return btf_ext_setup_info(btf_ext, &param);
} }
static int btf_ext_parse_hdr(__u8 *data, __u32 data_size) static int btf_ext_parse_hdr(__u8 *data, __u32 data_size)
...@@ -755,9 +750,7 @@ void btf_ext__free(struct btf_ext *btf_ext) ...@@ -755,9 +750,7 @@ void btf_ext__free(struct btf_ext *btf_ext)
{ {
if (!btf_ext) if (!btf_ext)
return; return;
free(btf_ext->data);
free(btf_ext->func_info.info);
free(btf_ext->line_info.info);
free(btf_ext); free(btf_ext);
} }
...@@ -774,13 +767,23 @@ struct btf_ext *btf_ext__new(__u8 *data, __u32 size) ...@@ -774,13 +767,23 @@ struct btf_ext *btf_ext__new(__u8 *data, __u32 size)
if (!btf_ext) if (!btf_ext)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
err = btf_ext_copy_func_info(btf_ext, data, size); btf_ext->data_size = size;
if (err) { btf_ext->data = malloc(size);
btf_ext__free(btf_ext); if (!btf_ext->data) {
return ERR_PTR(err); err = -ENOMEM;
goto done;
} }
memcpy(btf_ext->data, data, size);
err = btf_ext_copy_line_info(btf_ext, data, size); err = btf_ext_setup_func_info(btf_ext);
if (err)
goto done;
err = btf_ext_setup_line_info(btf_ext);
if (err)
goto done;
done:
if (err) { if (err) {
btf_ext__free(btf_ext); btf_ext__free(btf_ext);
return ERR_PTR(err); return ERR_PTR(err);
...@@ -789,6 +792,12 @@ struct btf_ext *btf_ext__new(__u8 *data, __u32 size) ...@@ -789,6 +792,12 @@ struct btf_ext *btf_ext__new(__u8 *data, __u32 size)
return btf_ext; return btf_ext;
} }
const void *btf_ext__get_raw_data(const struct btf_ext *btf_ext, __u32 *size)
{
*size = btf_ext->data_size;
return btf_ext->data;
}
static int btf_ext_reloc_info(const struct btf *btf, static int btf_ext_reloc_info(const struct btf *btf,
const struct btf_ext_info *ext_info, const struct btf_ext_info *ext_info,
const char *sec_name, __u32 insns_cnt, const char *sec_name, __u32 insns_cnt,
...@@ -837,7 +846,8 @@ static int btf_ext_reloc_info(const struct btf *btf, ...@@ -837,7 +846,8 @@ static int btf_ext_reloc_info(const struct btf *btf,
return -ENOENT; return -ENOENT;
} }
int btf_ext__reloc_func_info(const struct btf *btf, const struct btf_ext *btf_ext, int btf_ext__reloc_func_info(const struct btf *btf,
const struct btf_ext *btf_ext,
const char *sec_name, __u32 insns_cnt, const char *sec_name, __u32 insns_cnt,
void **func_info, __u32 *cnt) void **func_info, __u32 *cnt)
{ {
...@@ -845,7 +855,8 @@ int btf_ext__reloc_func_info(const struct btf *btf, const struct btf_ext *btf_ex ...@@ -845,7 +855,8 @@ int btf_ext__reloc_func_info(const struct btf *btf, const struct btf_ext *btf_ex
insns_cnt, func_info, cnt); insns_cnt, func_info, cnt);
} }
int btf_ext__reloc_line_info(const struct btf *btf, const struct btf_ext *btf_ext, int btf_ext__reloc_line_info(const struct btf *btf,
const struct btf_ext *btf_ext,
const char *sec_name, __u32 insns_cnt, const char *sec_name, __u32 insns_cnt,
void **line_info, __u32 *cnt) void **line_info, __u32 *cnt)
{ {
......
...@@ -57,6 +57,7 @@ struct btf_ext_header { ...@@ -57,6 +57,7 @@ struct btf_ext_header {
LIBBPF_API void btf__free(struct btf *btf); LIBBPF_API void btf__free(struct btf *btf);
LIBBPF_API struct btf *btf__new(__u8 *data, __u32 size); LIBBPF_API struct btf *btf__new(__u8 *data, __u32 size);
LIBBPF_API int btf__load(struct btf *btf);
LIBBPF_API __s32 btf__find_by_name(const struct btf *btf, LIBBPF_API __s32 btf__find_by_name(const struct btf *btf,
const char *type_name); const char *type_name);
LIBBPF_API __u32 btf__get_nr_types(const struct btf *btf); LIBBPF_API __u32 btf__get_nr_types(const struct btf *btf);
...@@ -65,8 +66,7 @@ LIBBPF_API const struct btf_type *btf__type_by_id(const struct btf *btf, ...@@ -65,8 +66,7 @@ LIBBPF_API const struct btf_type *btf__type_by_id(const struct btf *btf,
LIBBPF_API __s64 btf__resolve_size(const struct btf *btf, __u32 type_id); LIBBPF_API __s64 btf__resolve_size(const struct btf *btf, __u32 type_id);
LIBBPF_API int btf__resolve_type(const struct btf *btf, __u32 type_id); LIBBPF_API int btf__resolve_type(const struct btf *btf, __u32 type_id);
LIBBPF_API int btf__fd(const struct btf *btf); LIBBPF_API int btf__fd(const struct btf *btf);
LIBBPF_API void btf__get_strings(const struct btf *btf, const char **strings, LIBBPF_API const void *btf__get_raw_data(const struct btf *btf, __u32 *size);
__u32 *str_len);
LIBBPF_API const char *btf__name_by_offset(const struct btf *btf, __u32 offset); LIBBPF_API const char *btf__name_by_offset(const struct btf *btf, __u32 offset);
LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf); LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf);
LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name, LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name,
...@@ -76,6 +76,8 @@ LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name, ...@@ -76,6 +76,8 @@ LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name,
LIBBPF_API struct btf_ext *btf_ext__new(__u8 *data, __u32 size); LIBBPF_API struct btf_ext *btf_ext__new(__u8 *data, __u32 size);
LIBBPF_API void btf_ext__free(struct btf_ext *btf_ext); LIBBPF_API void btf_ext__free(struct btf_ext *btf_ext);
LIBBPF_API const void *btf_ext__get_raw_data(const struct btf_ext* btf_ext,
__u32 *size);
LIBBPF_API int btf_ext__reloc_func_info(const struct btf *btf, LIBBPF_API int btf_ext__reloc_func_info(const struct btf *btf,
const struct btf_ext *btf_ext, const struct btf_ext *btf_ext,
const char *sec_name, __u32 insns_cnt, const char *sec_name, __u32 insns_cnt,
......
...@@ -836,7 +836,7 @@ static int bpf_object__elf_collect(struct bpf_object *obj, int flags) ...@@ -836,7 +836,7 @@ static int bpf_object__elf_collect(struct bpf_object *obj, int flags)
obj->efile.maps_shndx = idx; obj->efile.maps_shndx = idx;
else if (strcmp(name, BTF_ELF_SEC) == 0) { else if (strcmp(name, BTF_ELF_SEC) == 0) {
obj->btf = btf__new(data->d_buf, data->d_size); obj->btf = btf__new(data->d_buf, data->d_size);
if (IS_ERR(obj->btf)) { if (IS_ERR(obj->btf) || btf__load(obj->btf)) {
pr_warning("Error loading ELF section %s: %ld. Ignored and continue.\n", pr_warning("Error loading ELF section %s: %ld. Ignored and continue.\n",
BTF_ELF_SEC, PTR_ERR(obj->btf)); BTF_ELF_SEC, PTR_ERR(obj->btf));
obj->btf = NULL; obj->btf = NULL;
......
...@@ -136,9 +136,11 @@ LIBBPF_0.0.2 { ...@@ -136,9 +136,11 @@ LIBBPF_0.0.2 {
btf__dedup; btf__dedup;
btf__get_map_kv_tids; btf__get_map_kv_tids;
btf__get_nr_types; btf__get_nr_types;
btf__get_strings; btf__get_raw_data;
btf__load;
btf_ext__free; btf_ext__free;
btf_ext__func_info_rec_size; btf_ext__func_info_rec_size;
btf_ext__get_raw_data;
btf_ext__line_info_rec_size; btf_ext__line_info_rec_size;
btf_ext__new; btf_ext__new;
btf_ext__reloc_func_info; btf_ext__reloc_func_info;
......
...@@ -5882,15 +5882,17 @@ static void dump_btf_strings(const char *strs, __u32 len) ...@@ -5882,15 +5882,17 @@ static void dump_btf_strings(const char *strs, __u32 len)
static int do_test_dedup(unsigned int test_num) static int do_test_dedup(unsigned int test_num)
{ {
const struct btf_dedup_test *test = &dedup_tests[test_num - 1]; const struct btf_dedup_test *test = &dedup_tests[test_num - 1];
int err = 0, i; __u32 test_nr_types, expect_nr_types, test_btf_size, expect_btf_size;
__u32 test_nr_types, expect_nr_types, test_str_len, expect_str_len; const struct btf_header *test_hdr, *expect_hdr;
void *raw_btf;
unsigned int raw_btf_size;
struct btf *test_btf = NULL, *expect_btf = NULL; struct btf *test_btf = NULL, *expect_btf = NULL;
const void *test_btf_data, *expect_btf_data;
const char *ret_test_next_str, *ret_expect_next_str; const char *ret_test_next_str, *ret_expect_next_str;
const char *test_strs, *expect_strs; const char *test_strs, *expect_strs;
const char *test_str_cur, *test_str_end; const char *test_str_cur, *test_str_end;
const char *expect_str_cur, *expect_str_end; const char *expect_str_cur, *expect_str_end;
unsigned int raw_btf_size;
void *raw_btf;
int err = 0, i;
fprintf(stderr, "BTF dedup test[%u] (%s):", test_num, test->descr); fprintf(stderr, "BTF dedup test[%u] (%s):", test_num, test->descr);
...@@ -5927,23 +5929,34 @@ static int do_test_dedup(unsigned int test_num) ...@@ -5927,23 +5929,34 @@ static int do_test_dedup(unsigned int test_num)
goto done; goto done;
} }
btf__get_strings(test_btf, &test_strs, &test_str_len); test_btf_data = btf__get_raw_data(test_btf, &test_btf_size);
btf__get_strings(expect_btf, &expect_strs, &expect_str_len); expect_btf_data = btf__get_raw_data(expect_btf, &expect_btf_size);
if (CHECK(test_str_len != expect_str_len, if (CHECK(test_btf_size != expect_btf_size,
"test_str_len:%u != expect_str_len:%u", "test_btf_size:%u != expect_btf_size:%u",
test_str_len, expect_str_len)) { test_btf_size, expect_btf_size)) {
err = -1;
goto done;
}
test_hdr = test_btf_data;
test_strs = test_btf_data + test_hdr->str_off;
expect_hdr = expect_btf_data;
expect_strs = expect_btf_data + expect_hdr->str_off;
if (CHECK(test_hdr->str_len != expect_hdr->str_len,
"test_hdr->str_len:%u != expect_hdr->str_len:%u",
test_hdr->str_len, expect_hdr->str_len)) {
fprintf(stderr, "\ntest strings:\n"); fprintf(stderr, "\ntest strings:\n");
dump_btf_strings(test_strs, test_str_len); dump_btf_strings(test_strs, test_hdr->str_len);
fprintf(stderr, "\nexpected strings:\n"); fprintf(stderr, "\nexpected strings:\n");
dump_btf_strings(expect_strs, expect_str_len); dump_btf_strings(expect_strs, expect_hdr->str_len);
err = -1; err = -1;
goto done; goto done;
} }
test_str_cur = test_strs; test_str_cur = test_strs;
test_str_end = test_strs + test_str_len; test_str_end = test_strs + test_hdr->str_len;
expect_str_cur = expect_strs; expect_str_cur = expect_strs;
expect_str_end = expect_strs + expect_str_len; expect_str_end = expect_strs + expect_hdr->str_len;
while (test_str_cur < test_str_end && expect_str_cur < expect_str_end) { while (test_str_cur < test_str_end && expect_str_cur < expect_str_end) {
size_t test_len, expect_len; size_t test_len, expect_len;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment