Print this page
10816 ctf_dwarf_convert_type() relies on un-initialized id
10817 ctfconvert -i option is mis-handled
10818 Improve ctfconvert error messages
10819 ctfconvert should handle empty dies
10820 ctfconvert -i never converts
10821 bad free in ctf_dwarf_init_die
10815 shouldn't build gcore.c as part of kmdb
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>

Split Close
Expand all
Collapse all
          --- old/usr/src/lib/libctf/common/ctf_dwarf.c
          +++ new/usr/src/lib/libctf/common/ctf_dwarf.c
↓ open down ↓ 875 lines elided ↑ open up ↑
 876  876          case DW_ATE_complex_float:
 877  877          case DW_ATE_imaginary_float:
 878  878          case DW_ATE_SUN_imaginary_float:
 879  879          case DW_ATE_SUN_interval_float:
 880  880                  *kindp = CTF_K_FLOAT;
 881  881                  if ((ret = ctf_dwarf_float_base(cup, type, enc)) != 0)
 882  882                          return (ret);
 883  883                  break;
 884  884          default:
 885  885                  (void) snprintf(cup->cu_errbuf, cup->cu_errlen,
 886      -                    "encountered unkown DWARF encoding: %d", type);
      886 +                    "encountered unknown DWARF encoding: %d", type);
 887  887                  return (ECTF_CONVBKERR);
 888  888          }
 889  889  
 890  890          return (0);
 891  891  }
 892  892  
 893  893  /*
 894  894   * Different compilers (at least GCC and Studio) use different names for types.
 895  895   * This parses the types and attempts to unify them. If this fails, we just fall
 896  896   * back to using the DWARF itself.
↓ open down ↓ 874 lines elided ↑ open up ↑
1771 1771                  ret = ctf_dwarf_create_reference(cup, die, idp, CTF_K_VOLATILE,
1772 1772                      isroot);
1773 1773                  break;
1774 1774          case DW_TAG_restrict_type:
1775 1775                  ctf_dprintf("restrict\n");
1776 1776                  ret = ctf_dwarf_create_reference(cup, die, idp, CTF_K_RESTRICT,
1777 1777                      isroot);
1778 1778                  break;
1779 1779          default:
1780 1780                  ctf_dprintf("ignoring tag type %x\n", tag);
     1781 +                *idp = CTF_ERR;
1781 1782                  ret = 0;
1782 1783                  break;
1783 1784          }
1784 1785          ctf_dprintf("ctf_dwarf_convert_type tag specific handler returned %d\n",
1785 1786              ret);
1786 1787  
1787 1788          return (ret);
1788 1789  }
1789 1790  
1790 1791  static int
↓ open down ↓ 879 lines elided ↑ open up ↑
2670 2671                  ctf_free(cdv, sizeof (ctf_dwvar_t));
2671 2672          }
2672 2673  
2673 2674          ctf_dprintf("Trying to free bitfields\n");
2674 2675          for (cdb = ctf_list_next(&cup->cu_bitfields); cdb != NULL; cdb = ndb) {
2675 2676                  ndb = ctf_list_next(cdb);
2676 2677                  ctf_free(cdb, sizeof (ctf_dwbitf_t));
2677 2678          }
2678 2679  
2679 2680          ctf_dprintf("Trying to clean up dwarf_t: %p\n", cup->cu_dwarf);
2680      -        (void) dwarf_finish(cup->cu_dwarf, &derr);
     2681 +        if (cup->cu_dwarf != NULL)
     2682 +                (void) dwarf_finish(cup->cu_dwarf, &derr);
2681 2683          cup->cu_dwarf = NULL;
2682 2684          ctf_close(cup->cu_ctfp);
2683 2685  
2684 2686          cookie = NULL;
2685 2687          while ((map = avl_destroy_nodes(&cup->cu_map, &cookie)) != NULL) {
2686 2688                  ctf_free(map, sizeof (ctf_dwmap_t));
2687 2689          }
2688 2690          avl_destroy(&cup->cu_map);
2689 2691          cup->cu_errbuf = NULL;
2690 2692  }
↓ open down ↓ 29 lines elided ↑ open up ↑
2720 2722                  }
2721 2723  
2722 2724                  if (vers != DWARF_VERSION_TWO) {
2723 2725                          (void) snprintf(errbuf, errlen,
2724 2726                              "unsupported DWARF version: %d\n", vers);
2725 2727                          return (ECTF_CONVBKERR);
2726 2728                  }
2727 2729                  *ndies = *ndies + 1;
2728 2730          }
2729 2731  
2730      -        if (*ndies == 0) {
2731      -                (void) snprintf(errbuf, errlen,
2732      -                    "file does not contain valid DWARF data: %s\n",
2733      -                    dwarf_errmsg(*derr));
2734      -                return (ECTF_CONVBKERR);
2735      -        }
2736      -
2737 2732          return (0);
2738 2733  }
2739 2734  
2740 2735  static int
2741 2736  ctf_dwarf_init_die(int fd, Elf *elf, ctf_cu_t *cup, int ndie, char *errbuf,
2742 2737      size_t errlen)
2743 2738  {
2744 2739          int ret;
2745 2740          Dwarf_Unsigned hdrlen, abboff, nexthdr;
2746 2741          Dwarf_Half addrsz;
↓ open down ↓ 15 lines elided ↑ open up ↑
2762 2757  
2763 2758                  /*
2764 2759                   * Compilers are apparently inconsistent. Some emit no DWARF for
2765 2760                   * empty files and others emit empty compilation unit.
2766 2761                   */
2767 2762                  cup->cu_voidtid = CTF_ERR;
2768 2763                  cup->cu_longtid = CTF_ERR;
2769 2764                  cup->cu_elf = elf;
2770 2765                  cup->cu_maxoff = nexthdr - 1;
2771 2766                  cup->cu_ctfp = ctf_fdcreate(fd, &ret);
2772      -                if (cup->cu_ctfp == NULL) {
2773      -                        ctf_free(cup, sizeof (ctf_cu_t));
     2767 +                if (cup->cu_ctfp == NULL)
2774 2768                          return (ret);
2775      -                }
     2769 +
2776 2770                  avl_create(&cup->cu_map, ctf_dwmap_comp, sizeof (ctf_dwmap_t),
2777 2771                      offsetof(ctf_dwmap_t, cdm_avl));
2778 2772                  cup->cu_errbuf = errbuf;
2779 2773                  cup->cu_errlen = errlen;
2780 2774                  bzero(&cup->cu_vars, sizeof (ctf_list_t));
2781 2775                  bzero(&cup->cu_funcs, sizeof (ctf_list_t));
2782 2776                  bzero(&cup->cu_bitfields, sizeof (ctf_list_t));
2783 2777  
2784 2778                  if ((ret = ctf_dwarf_die_elfenc(elf, cup, errbuf,
2785      -                    errlen)) != 0) {
2786      -                        avl_destroy(&cup->cu_map);
2787      -                        ctf_free(cup, sizeof (ctf_cu_t));
     2779 +                    errlen)) != 0)
2788 2780                          return (ret);
2789      -                }
2790 2781  
2791      -                if ((ret = ctf_dwarf_sib(cup, NULL, &cu)) != 0) {
2792      -                        avl_destroy(&cup->cu_map);
2793      -                        ctf_free(cup, sizeof (ctf_cu_t));
     2782 +                if ((ret = ctf_dwarf_sib(cup, NULL, &cu)) != 0)
2794 2783                          return (ret);
2795      -                }
     2784 +
2796 2785                  if (cu == NULL) {
2797 2786                          (void) snprintf(errbuf, errlen,
2798      -                            "file does not contain DWARF data\n");
2799      -                        avl_destroy(&cup->cu_map);
2800      -                        ctf_free(cup, sizeof (ctf_cu_t));
2801      -                        return (ECTF_CONVBKERR);
     2787 +                            "file does not contain DWARF data");
     2788 +                        return (ECTF_CONVNODEBUG);
2802 2789                  }
2803 2790  
2804      -                if ((ret = ctf_dwarf_child(cup, cu, &child)) != 0) {
2805      -                        avl_destroy(&cup->cu_map);
2806      -                        ctf_free(cup, sizeof (ctf_cu_t));
     2791 +                if ((ret = ctf_dwarf_child(cup, cu, &child)) != 0)
2807 2792                          return (ret);
2808      -                }
     2793 +
2809 2794                  if (child == NULL) {
2810 2795                          (void) snprintf(errbuf, errlen,
2811      -                            "file does not contain DWARF data\n");
2812      -                        avl_destroy(&cup->cu_map);
2813      -                        ctf_free(cup, sizeof (ctf_cu_t));
2814      -                        return (ECTF_CONVBKERR);
     2796 +                            "file does not contain DWARF data");
     2797 +                        return (ECTF_CONVNODEBUG);
2815 2798                  }
2816 2799  
2817 2800                  cup->cu_cuoff = offset;
2818 2801                  cup->cu_cu = child;
2819 2802  
2820      -                if ((cup->cu_cmh = ctf_merge_init(fd, &ret)) == NULL) {
2821      -                        avl_destroy(&cup->cu_map);
2822      -                        ctf_free(cup, sizeof (ctf_cu_t));
     2803 +                if ((cup->cu_cmh = ctf_merge_init(fd, &ret)) == NULL)
2823 2804                          return (ret);
2824      -                }
2825 2805  
2826 2806                  if (ctf_dwarf_string(cup, cu, DW_AT_name, &name) == 0) {
2827 2807                          size_t len = strlen(name) + 1;
2828 2808                          char *b = basename(name);
2829 2809                          cup->cu_name = strdup(b);
2830 2810                          ctf_free(name, len);
2831 2811                  }
2832 2812                  break;
2833 2813          }
2834 2814  
2835 2815          return (0);
2836 2816  }
2837 2817  
     2818 +/*
     2819 + * This is our only recourse to identify a C source file that is missing debug
     2820 + * info: it will be mentioned as an STT_FILE, but not have a compile unit entry.
     2821 + * (A traditional ctfmerge works on individual files, so can identify missing
     2822 + * DWARF more directly, via ctf_has_c_source() on the .o file.)
     2823 + *
     2824 + * As we operate on basenames, this can of course miss some cases, but it's
     2825 + * better than not checking at all.
     2826 + *
     2827 + * We explicitly whitelist some CRT components.  Failing that, there's always
     2828 + * the -m option.
     2829 + */
     2830 +static boolean_t
     2831 +c_source_has_debug(const char *file, ctf_cu_t *cus, size_t nr_cus)
     2832 +{
     2833 +        const char *basename = strrchr(file, '/');
2838 2834  
2839      -ctf_conv_status_t
2840      -ctf_dwarf_convert(int fd, Elf *elf, uint_t nthrs, int *errp, ctf_file_t **fpp,
     2835 +        if (basename == NULL)
     2836 +                basename = file;
     2837 +        else
     2838 +                basename++;
     2839 +
     2840 +        if (strcmp(basename, "common-crt.c") == 0 ||
     2841 +            strcmp(basename, "gmon.c") == 0 ||
     2842 +            strcmp(basename, "dlink_init.c") == 0 ||
     2843 +            strcmp(basename, "dlink_common.c") == 0 ||
     2844 +            strncmp(basename, "crt", strlen("crt")) == 0 ||
     2845 +            strncmp(basename, "values-", strlen("values-")) == 0)
     2846 +                return (B_TRUE);
     2847 +
     2848 +        for (size_t i = 0; i < nr_cus; i++) {
     2849 +                if (strcmp(basename, cus[i].cu_name) == 0)
     2850 +                        return (B_TRUE);
     2851 +        }
     2852 +
     2853 +        return (B_FALSE);
     2854 +}
     2855 +
     2856 +static int
     2857 +ctf_dwarf_check_missing(ctf_cu_t *cus, size_t nr_cus, Elf *elf,
2841 2858      char *errmsg, size_t errlen)
2842 2859  {
     2860 +        Elf_Scn *scn, *strscn;
     2861 +        Elf_Data *data, *strdata;
     2862 +        GElf_Shdr shdr;
     2863 +        ulong_t i;
     2864 +
     2865 +        scn = NULL;
     2866 +        while ((scn = elf_nextscn(elf, scn)) != NULL) {
     2867 +                if (gelf_getshdr(scn, &shdr) == NULL) {
     2868 +                        (void) snprintf(errmsg, errlen,
     2869 +                            "failed to get section header: %s\n",
     2870 +                            elf_errmsg(elf_errno()));
     2871 +                        return (EINVAL);
     2872 +                }
     2873 +
     2874 +                if (shdr.sh_type == SHT_SYMTAB)
     2875 +                        break;
     2876 +        }
     2877 +
     2878 +        if (scn == NULL)
     2879 +                return (0);
     2880 +
     2881 +        if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL) {
     2882 +                (void) snprintf(errmsg, errlen,
     2883 +                    "failed to get str section: %s\n",
     2884 +                    elf_errmsg(elf_errno()));
     2885 +                return (EINVAL);
     2886 +        }
     2887 +
     2888 +        if ((data = elf_getdata(scn, NULL)) == NULL) {
     2889 +                (void) snprintf(errmsg, errlen, "failed to read section: %s\n",
     2890 +                    elf_errmsg(elf_errno()));
     2891 +                return (EINVAL);
     2892 +        }
     2893 +
     2894 +        if ((strdata = elf_getdata(strscn, NULL)) == NULL) {
     2895 +                (void) snprintf(errmsg, errlen,
     2896 +                    "failed to read string table: %s\n",
     2897 +                    elf_errmsg(elf_errno()));
     2898 +                return (EINVAL);
     2899 +        }
     2900 +
     2901 +        for (i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
     2902 +                GElf_Sym sym;
     2903 +                const char *file;
     2904 +                size_t len;
     2905 +
     2906 +                if (gelf_getsym(data, i, &sym) == NULL) {
     2907 +                        (void) snprintf(errmsg, errlen,
     2908 +                            "failed to read sym %lu: %s\n",
     2909 +                            i, elf_errmsg(elf_errno()));
     2910 +                        return (EINVAL);
     2911 +                }
     2912 +
     2913 +                if (GELF_ST_TYPE(sym.st_info) != STT_FILE)
     2914 +                        continue;
     2915 +
     2916 +                file = (const char *)((uintptr_t)strdata->d_buf + sym.st_name);
     2917 +                len = strlen(file);
     2918 +                if (len < 2 || strncmp(".c", &file[len - 2], 2) != 0)
     2919 +                        continue;
     2920 +
     2921 +                if (!c_source_has_debug(file, cus, nr_cus)) {
     2922 +                        (void) snprintf(errmsg, errlen,
     2923 +                            "file %s is missing debug info\n", file);
     2924 +                        return (ECTF_CONVNODEBUG);
     2925 +                }
     2926 +        }
     2927 +
     2928 +        return (0);
     2929 +}
     2930 +
     2931 +int
     2932 +ctf_dwarf_convert(int fd, Elf *elf, uint_t nthrs, uint_t flags,
     2933 +    ctf_file_t **fpp, char *errbuf, size_t errlen)
     2934 +{
2843 2935          int err, ret, ndies, i;
2844 2936          Dwarf_Debug dw;
2845 2937          Dwarf_Error derr;
2846 2938          ctf_cu_t *cdies = NULL, *cup;
2847 2939          workq_t *wqp = NULL;
2848 2940  
2849      -        if (errp == NULL)
2850      -                errp = &err;
2851      -        *errp = 0;
2852 2941          *fpp = NULL;
2853 2942  
2854 2943          ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dw, &derr);
2855 2944          if (ret != DW_DLV_OK) {
2856      -                /*
2857      -                 * We may want to expect DWARF data here and fail conversion if
2858      -                 * it's missing. In this case, if we actually have some amount
2859      -                 * of DWARF, but no section, for now, just go ahead and create
2860      -                 * an empty CTF file.
2861      -                 */
2862 2945                  if (ret == DW_DLV_NO_ENTRY ||
2863 2946                      dwarf_errno(derr) == DW_DLE_DEBUG_INFO_NULL) {
2864      -                        *fpp = ctf_create(errp);
2865      -                        return (*fpp != NULL ? CTF_CONV_SUCCESS :
2866      -                            CTF_CONV_ERROR);
     2947 +                        (void) snprintf(errbuf, errlen,
     2948 +                            "file does not contain DWARF data\n");
     2949 +                        return (ECTF_CONVNODEBUG);
2867 2950                  }
2868      -                (void) snprintf(errmsg, errlen,
2869      -                    "failed to initialize DWARF: %s\n",
2870      -                    dwarf_errmsg(derr));
2871      -                *errp = ECTF_CONVBKERR;
2872      -                return (CTF_CONV_ERROR);
     2951 +
     2952 +                (void) snprintf(errbuf, errlen,
     2953 +                    "dwarf_elf_init() failed: %s\n", dwarf_errmsg(derr));
     2954 +                return (ECTF_CONVBKERR);
2873 2955          }
2874 2956  
2875 2957          /*
2876 2958           * Iterate over all of the compilation units and create a ctf_cu_t for
2877 2959           * each of them.  This is used to determine if we have zero, one, or
2878 2960           * multiple dies to convert. If we have zero, that's an error. If
2879 2961           * there's only one die, that's the simple case.  No merge needed and
2880 2962           * only a single Dwarf_Debug as well.
2881 2963           */
2882 2964          ndies = 0;
2883      -        ret = ctf_dwarf_count_dies(dw, &derr, &ndies, errmsg, errlen);
2884      -        if (ret != 0) {
2885      -                *errp = ret;
2886      -                goto out;
     2965 +        err = ctf_dwarf_count_dies(dw, &derr, &ndies, errbuf, errlen);
     2966 +
     2967 +        ctf_dprintf("found %d DWARF CUs\n", ndies);
     2968 +
     2969 +        if (ndies == 0) {
     2970 +                (void) snprintf(errbuf, errlen,
     2971 +                    "file does not contain DWARF data\n");
     2972 +                return (ECTF_CONVNODEBUG);
2887 2973          }
2888 2974  
2889 2975          (void) dwarf_finish(dw, &derr);
2890 2976          cdies = ctf_alloc(sizeof (ctf_cu_t) * ndies);
2891 2977          if (cdies == NULL) {
2892      -                *errp = ENOMEM;
2893      -                return (CTF_CONV_ERROR);
     2978 +                return (ENOMEM);
2894 2979          }
2895 2980  
     2981 +        bzero(cdies, sizeof (ctf_cu_t) * ndies);
     2982 +
2896 2983          for (i = 0; i < ndies; i++) {
2897 2984                  cup = &cdies[i];
2898 2985                  ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL,
2899 2986                      &cup->cu_dwarf, &derr);
2900 2987                  if (ret != 0) {
2901 2988                          ctf_free(cdies, sizeof (ctf_cu_t) * ndies);
2902      -                        (void) snprintf(errmsg, errlen,
     2989 +                        (void) snprintf(errbuf, errlen,
2903 2990                              "failed to initialize DWARF: %s\n",
2904 2991                              dwarf_errmsg(derr));
2905      -                        *errp = ECTF_CONVBKERR;
2906      -                        return (CTF_CONV_ERROR);
     2992 +                        return (ECTF_CONVBKERR);
2907 2993                  }
2908 2994  
2909      -                ret = ctf_dwarf_init_die(fd, elf, &cdies[i], i, errmsg, errlen);
2910      -                if (ret != 0) {
2911      -                        *errp = ret;
     2995 +                err = ctf_dwarf_init_die(fd, elf, cup, i, errbuf, errlen);
     2996 +                if (err != 0)
2912 2997                          goto out;
2913      -                }
2914 2998  
2915 2999                  cup->cu_doweaks = ndies > 1 ? B_FALSE : B_TRUE;
2916 3000          }
2917 3001  
2918      -        ctf_dprintf("found %d DWARF CUs\n", ndies);
     3002 +        if (!(flags & CTF_ALLOW_MISSING_DEBUG) &&
     3003 +            (err = ctf_dwarf_check_missing(cdies, ndies,
     3004 +            elf, errbuf, errlen)) != 0)
     3005 +                goto out;
2919 3006  
2920 3007          /*
2921 3008           * If we only have one compilation unit, there's no reason to use
2922 3009           * multiple threads, even if the user requested them. After all, they
2923 3010           * just gave us an upper bound.
2924 3011           */
2925 3012          if (ndies == 1)
2926 3013                  nthrs = 1;
2927 3014  
2928 3015          if (workq_init(&wqp, nthrs) == -1) {
2929      -                *errp = errno;
     3016 +                err = errno;
2930 3017                  goto out;
2931 3018          }
2932 3019  
2933 3020          for (i = 0; i < ndies; i++) {
2934 3021                  cup = &cdies[i];
2935 3022                  ctf_dprintf("adding cu %s: %p, %x %x\n", cup->cu_name,
2936 3023                      cup->cu_cu, cup->cu_cuoff, cup->cu_maxoff);
2937 3024                  if (workq_add(wqp, cup) == -1) {
2938      -                        *errp = errno;
     3025 +                        err = errno;
2939 3026                          goto out;
2940 3027                  }
2941 3028          }
2942 3029  
2943      -        ret = workq_work(wqp, ctf_dwarf_convert_one, NULL, errp);
     3030 +        ret = workq_work(wqp, ctf_dwarf_convert_one, NULL, &err);
2944 3031          if (ret == WORKQ_ERROR) {
2945      -                *errp = errno;
     3032 +                err = errno;
2946 3033                  goto out;
2947 3034          } else if (ret == WORKQ_UERROR) {
2948 3035                  ctf_dprintf("internal convert failed: %s\n",
2949      -                    ctf_errmsg(*errp));
     3036 +                    ctf_errmsg(err));
2950 3037                  goto out;
2951 3038          }
2952 3039  
2953 3040          ctf_dprintf("Determining next phase: have %d CUs\n", ndies);
2954 3041          if (ndies != 1) {
2955 3042                  ctf_merge_t *cmp;
2956 3043  
2957      -                cmp = ctf_merge_init(fd, &ret);
2958      -                if (cmp == NULL) {
2959      -                        *errp = ret;
     3044 +                cmp = ctf_merge_init(fd, &err);
     3045 +                if (cmp == NULL)
2960 3046                          goto out;
2961      -                }
2962 3047  
2963 3048                  ctf_dprintf("setting threads\n");
2964      -                if ((ret = ctf_merge_set_nthreads(cmp, nthrs)) != 0) {
     3049 +                if ((err = ctf_merge_set_nthreads(cmp, nthrs)) != 0) {
2965 3050                          ctf_merge_fini(cmp);
2966      -                        *errp = ret;
2967 3051                          goto out;
2968 3052                  }
2969 3053  
2970 3054                  for (i = 0; i < ndies; i++) {
2971 3055                          cup = &cdies[i];
2972      -                        ctf_dprintf("adding cu %s (%p)\n", cup->cu_name,
2973      -                            cup->cu_ctfp);
2974      -                        if ((ret = ctf_merge_add(cmp, cup->cu_ctfp)) != 0) {
     3056 +                        if ((err = ctf_merge_add(cmp, cup->cu_ctfp)) != 0) {
2975 3057                                  ctf_merge_fini(cmp);
2976      -                                *errp = ret;
2977 3058                                  goto out;
2978 3059                          }
2979 3060                  }
2980 3061  
2981 3062                  ctf_dprintf("performing merge\n");
2982      -                ret = ctf_merge_merge(cmp, fpp);
2983      -                if (ret != 0) {
     3063 +                err = ctf_merge_merge(cmp, fpp);
     3064 +                if (err != 0) {
2984 3065                          ctf_dprintf("failed merge!\n");
2985 3066                          *fpp = NULL;
2986 3067                          ctf_merge_fini(cmp);
2987      -                        *errp = ret;
2988 3068                          goto out;
2989 3069                  }
2990 3070                  ctf_merge_fini(cmp);
2991      -                *errp = 0;
     3071 +                err = 0;
2992 3072                  ctf_dprintf("successfully converted!\n");
2993 3073          } else {
2994      -                *errp = 0;
     3074 +                err = 0;
2995 3075                  *fpp = cdies->cu_ctfp;
2996 3076                  cdies->cu_ctfp = NULL;
2997 3077                  ctf_dprintf("successfully converted!\n");
2998 3078          }
2999 3079  
3000 3080  out:
3001 3081          workq_fini(wqp);
3002 3082          ctf_dwarf_free_dies(cdies, ndies);
3003      -        return (*fpp != NULL ? CTF_CONV_SUCCESS : CTF_CONV_ERROR);
     3083 +        return (err);
3004 3084  }
    
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX